lorid

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

commit e79a2f3368a2880914ebb46945df5375a83ffbc3
parent afe2313c5a3b621d17ab59f875f317c88445d7d2
Author: nibo <nibo@relim.de>
Date:   Sat,  8 Feb 2025 18:42:58 +0100

Add src/* files

Diffstat:
Asrc/chord_diagram.c | 740+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/chord_diagram.h | 26++++++++++++++++++++++++++
Asrc/chordpro.c | 5470+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/chordpro.h | 175+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/config.c | 1065+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/config.h | 17+++++++++++++++++
Asrc/diagrams.h | 4394+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lorid.c | 117+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/out_pdf.c | 2817+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/out_pdf.h | 103+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/types.h | 338+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/util.c | 502+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/util.h | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
13 files changed, 15823 insertions(+), 0 deletions(-)

diff --git a/src/chord_diagram.c b/src/chord_diagram.c @@ -0,0 +1,740 @@ +#include <string.h> +#include <ctype.h> +#include <pdfio.h> +#include <pdfio-content.h> +#include "types.h" +#include "out_pdf.h" +#include "util.h" +#include "config.h" +#include "chordpro.h" +#include "chord_diagram.h" +#include "diagrams.h" + +static bool +text_show( + pdfio_stream_t *stream, + const char *text, + double x, + double y +) +{ + if (!pdfioContentSetTextRenderingMode(stream, PDFIO_TEXTRENDERING_FILL)) { + fprintf(stderr, "pdfioContentSetTextRenderingMode failed.\n"); + return false; + } + if (!pdfioContentTextBegin(stream)) { + fprintf(stderr, "pdfioContentTextBegin failed.\n"); + return false; + } + if (!pdfioContentTextMoveTo(stream, x, y)) { + fprintf(stderr, "pdfioContentTextMoveTo failed.\n"); + return false; + } + if (!pdfioContentTextShow(stream, true, text)) { + fprintf(stderr, "pdfioContentTextShow failed.\n"); + return false; + } + if (!pdfioContentTextEnd(stream)) { + fprintf(stderr, "pdfioContentTextEnd failed.\n"); + return false; + } + return true; +} + +static bool +draw_rectangle( + pdfio_stream_t *stream, + double x, + double y, + double width, + double height, + enum TextRendering type +) +{ + if (!pdfioContentSetFillColorRGB(stream, 0.0, 0.0, 0.0)) { + fprintf(stderr, "pdfioContentSetFillColorRGB failed.\n"); + return false; + } + if (!pdfioContentSetStrokeColorRGB(stream, 0.0, 0.0, 0.0)) { + fprintf(stderr, "pdfioContentSetStrokeColorRGB failed.\n"); + return false; + } + if (!pdfioContentPathRect(stream, x, y, width, height)) { + fprintf(stderr, "pdfioContentPathRect failed.\n"); + return false; + } + switch (type) { + case FILL_AND_STROKE: + if (!pdfioContentFillAndStroke(stream, true)) { + fprintf(stderr, "pdfioContentFillAndStroke failed.\n"); + return false; + } + break; + case FILL: + if (!pdfioContentFill(stream, true)) { + fprintf(stderr, "pdfioContentFill failed.\n"); + return false; + } + break; + case STROKE: + if (!pdfioContentStroke(stream)) { + fprintf(stderr, "pdfioContentStroke failed.\n"); + return false; + } + break; + } + return true; +} + +static bool +draw_line( + pdfio_stream_t *stream, + double x, + double y, + double width, + enum Direction direction +) +{ + if (!pdfioContentPathMoveTo(stream, x, y)) { + fprintf(stderr, "pdfioContentPathMoveTo failed.\n"); + return false; + } + if (direction == HORIZONTAL) { + if (!pdfioContentPathLineTo(stream, x+width, y)) { + fprintf(stderr, "pdfioContentPathLineTo failed.\n"); + return false; + } + } else { + if (!pdfioContentPathLineTo(stream, x, y+width)) { + fprintf(stderr, "pdfioContentPathLineTo failed.\n"); + return false; + } + } + if (!pdfioContentSetStrokeColorRGB(stream, 0.0, 0.0, 0.0)) { + fprintf(stderr, "pdfioContentSetStrokeColorRGB failed.\n"); + return false; + } + if (!pdfioContentStroke(stream)) { + fprintf(stderr, "pdfioContentFill failed.\n"); + return false; + } + return true; +} + +static bool +draw_bezier_oval_quarter( + pdfio_stream_t *stream, + double center_x, + double center_y, + double size_x, + double size_y +) +{ + if (!pdfioContentPathMoveTo(stream, center_x - size_x, center_y)) { + fprintf(stderr, "pdfioContentPathMoveTo failed.\n"); + return false; + } + if (!pdfioContentPathCurve( + stream, + center_x - size_x, + center_y - 0.552 * size_y, + center_x - 0.552 * size_x, + center_y - size_y, + center_x, + center_y - size_y + )) { + fprintf(stderr, "pdfioContentPathCurve failed.\n"); + return false; + } + return true; +} + +static bool +draw_bezier_oval( + pdfio_stream_t *stream, + double center_x, + double center_y, + double size_x, + double size_y +) +{ + if (!pdfioContentSetStrokeColorRGB(stream, 0.0, 0.0, 0.0)) { + fprintf(stderr, "pdfioContentSetStrokeColorRGB failed.\n"); + return false; + } + if (!draw_bezier_oval_quarter(stream, center_x, center_y, -size_x, size_y)) { + fprintf(stderr, "draw_bezier_oval_quarter failed."); + return false; + } + if (!draw_bezier_oval_quarter(stream, center_x, center_y, size_x, size_y)) { + fprintf(stderr, "draw_bezier_oval_quarter failed."); + return false; + } + if (!draw_bezier_oval_quarter(stream, center_x, center_y, size_x, -size_y)) { + fprintf(stderr, "draw_bezier_oval_quarter failed."); + return false; + } + if (!draw_bezier_oval_quarter(stream, center_x, center_y, -size_x, -size_y)) { + fprintf(stderr, "draw_bezier_oval_quarter failed."); + return false; + } + if (!pdfioContentStroke(stream)) { + fprintf(stderr, "pdfioContentStroke failed.\n"); + return false; + } + return true; +} + +static bool +draw_bezier_circle( + pdfio_stream_t *stream, + double center_x, + double center_y, + double size +) +{ + if (!pdfioContentSetLineWidth(stream, size * 0.4)) { + fprintf(stderr, "pdfioContentSetLineWidth failed.\n"); + return false; + } + if (!draw_bezier_oval(stream, center_x, center_y, size, size)) { + fprintf(stderr, "draw_bezier_oval failed."); + return false; + } + return true; +} + +static bool +draw_x(pdfio_stream_t *stream, double x, double y, double size) +{ + if (!pdfioContentSetLineWidth(stream, size * 0.4)) { + fprintf(stderr, "pdfioContentSetLineWidth failed.\n"); + return false; + } + if (!pdfioContentSetStrokeColorRGB(stream, 0.0, 0.0, 0.0)) { + fprintf(stderr, "pdfioContentSetStrokeColorRGB failed.\n"); + return false; + } + if (!pdfioContentPathMoveTo(stream, x - size, y - size)) { + fprintf(stderr, "pdfioContentPathMoveTo failed.\n"); + return false; + } + if (!pdfioContentPathLineTo(stream, x + size, y + size)) { + fprintf(stderr, "pdfioContentPathLineTo failed.\n"); + return false; + } + if (!pdfioContentStroke(stream)) { + fprintf(stderr, "pdfioContentFill failed.\n"); + return false; + } + if (!pdfioContentPathMoveTo(stream, x - size, y + size)) { + fprintf(stderr, "pdfioContentPathMoveTo failed.\n"); + return false; + } + if (!pdfioContentPathLineTo(stream, x + size, y - size)) { + fprintf(stderr, "pdfioContentPathLineTo failed.\n"); + return false; + } + if (!pdfioContentStroke(stream)) { + fprintf(stderr, "pdfioContentFill failed.\n"); + return false; + } + return true; +} + +static bool +is_valid_circle_char(char c) +{ + if (c >= '/' && c <= '9') { + return true; + } + if (c >= 'A' && c <= 'Z') { + return true; + } + return false; +} + +static bool +draw_circle_with_char_inside( + pdfio_stream_t *stream, + double x, + double y, + double field_width, + char c +) +{ + if (!is_valid_circle_char(c)) { + fprintf(stderr, "is_valid_circle_char failed.\n"); + return false; + } + char str[2]; + str[0] = c; + str[1] = 0; + if (!pdfioContentSetTextFont(stream, "chord-diagram-symbols", field_width)) { + fprintf(stderr, "pdfioContentSetTextFont failed.\n"); + return false; + } + /* INFO: Needs to be the color of the page background. This cannot be set at the moment. It will always be white. */ + if (!pdfioContentSetFillColorRGB(stream, 1.0, 1.0, 1.0)) { + fprintf(stderr, "pdfioContentSetFillColorRGB failed.\n"); + return false; + } + if (!text_show(stream, "/", x-field_width/2, y-field_width/2.8)) { + fprintf(stderr, "text_show failed."); + return false; + } + if (!pdfioContentSetFillColorRGB(stream, 0.0, 0.0, 0.0)) { + fprintf(stderr, "pdfioContentSetFillColorRGB failed.\n"); + return false; + } + if (!text_show(stream, (char *)&str, x-field_width/2, y-field_width/2.8)) { + fprintf(stderr, "text_show failed."); + return false; + } + return true; +} + +struct StringDiagram * +string_diagram_new(void) +{ + struct StringDiagram *d = emalloc(sizeof(struct StringDiagram)); + d->name = NULL; + d->base_fret = -2; + memset(d->frets, -2, sizeof(d->frets)); + memset(d->fingers, -2, sizeof(d->fingers)); + return d; +} + +static int +string_diagram_string_count(struct StringDiagram *d) +{ + int i; + for (i = 0; d->frets[i] != -2 && i < 12; i++); + return i; +} + +static void +string_diagram_free(struct StringDiagram *d) +{ + free(d->name); + free(d); +} + +static struct StringDiagram * +string_diagram_copy_all_but_name(struct StringDiagram *diagram) +{ + struct StringDiagram *copy = emalloc(sizeof(struct StringDiagram)); + copy->base_fret = diagram->base_fret; + int i; + for (i = 0; i<12; i++) { + copy->frets[i] = diagram->frets[i]; + } + for (i = 0; i<12; i++) { + copy->fingers[i] = diagram->fingers[i]; + } + return copy; +} + +/* static bool +string_diagram_is_valid(struct StringDiagram *d) +{ + int i; + size_t fret_count, finger_count; + if (d->base_fret < 1 || d->base_fret > 99) { + return false; + } + i = 0; + while (d->frets[i] != -2 && i < 12) { + i++; + } + fret_count = i; + i = 0; + while (d->fingers[i] != -2 && i < 12) { + i++; + } + finger_count = i; + if (finger_count > 0 && fret_count != finger_count) { + return false; + } + return true; +} */ + +static size_t +string_diagram_fret_count(struct StringDiagram *d) +{ + int i; + for (i = 0; d->frets[i] != -2 && i < 12; i++); + return i; +} + +static char +finger_to_char(int8_t finger) +{ + switch (finger) { + case 1: + return '1'; + case 2: + return '2'; + case 3: + return '3'; + case 4: + return '4'; + default: + return '/'; + } +} + +static bool +string_diagram_draw( + pdfio_stream_t *stream, + struct StringDiagram *diagram, + double x, + double y, + double width +) +{ + int i; + int instrument_string_count = string_diagram_string_count(diagram); + double field_width = width / (instrument_string_count - 1); + double height = field_width * 4; + double y_above_diagram = y + height + field_width / 2 + field_width / 2.5; + double vertical_lines_height = height + field_width / 2; + if (!pdfioContentSetLineWidth(stream, field_width * 0.09)) { + fprintf(stderr, "pdfioContentSetLineWidth failed.\n"); + return false; + } + if (!draw_rectangle(stream, x, y, width, height, STROKE)) { + fprintf(stderr, "draw_rectangle failed.\n"); + return false; + } + // from bottom to top draw horizontal lines + for (i = 0; i<4; i++) { + if (!draw_line(stream, x, y+field_width * (i+1), width, HORIZONTAL)) { + fprintf(stderr, "draw_line failed.\n"); + return false; + } + } + // from left to right draw vertical lines + for (i = 0; i<instrument_string_count; i++) { + if (!draw_line(stream, x+field_width*i, y, vertical_lines_height, VERTICAL)) { + fprintf(stderr, "draw_line failed.\n"); + return false; + } + } + if (diagram->base_fret == 1) { + if (!draw_rectangle(stream, x, y+height, width, field_width/2, FILL_AND_STROKE)) { + fprintf(stderr, "draw_rectangle failed.\n"); + return false; + } + } else { + double base_pos_x; + char base_position[5]; + base_position[4] = 0; + sprintf((char *)&base_position, "%d", diagram->base_fret); + if (diagram->base_fret > 9) { + base_pos_x = x - field_width - field_width; + } else { + base_pos_x = x - field_width - field_width / 3; + } + if (!pdfioContentSetTextCharacterSpacing(stream, -1.5)) { + fprintf(stderr, "pdfioContentSetTextCharacterSpacing failed.\n"); + return false; + } + if (!pdfioContentSetTextFont(stream, "chord-diagram-regular-font", field_width*1.15)) { + fprintf(stderr, "pdfioContentSetTextFont failed.\n"); + return false; + } + if (!text_show(stream, base_position, base_pos_x, y+field_width*3+field_width*0.1)) { + fprintf(stderr, "text_show failed.\n"); + return false; + } + } + int8_t fret, finger; + for (i = 0; i<instrument_string_count; i++) { + fret = diagram->frets[i]; + finger = diagram->fingers[i]; + if (fret == -1) { + if (!draw_x(stream, x+(i*field_width), y_above_diagram, field_width / 4)) { + fprintf(stderr, "draw_x failed.\n"); + return false; + } + } else + if (fret == 0) { + if (!draw_bezier_circle(stream, x+(i*field_width), y_above_diagram, field_width / 4)) { + fprintf(stderr, "draw_circle_with_char_inside failed.\n"); + return false; + } + } else + if (fret < 5) { + if (!draw_circle_with_char_inside(stream, + x + i * field_width, + y + field_width/2 + field_width * (4 - fret), + field_width, + finger_to_char(finger) + )) { + fprintf(stderr, "draw_circle_with_char_inside failed.\n"); + return false; + } + } + } + pdfio_obj_t *font_obj; + double name_width, centered_x; + font_obj = out_pdf_fnt_obj_get_by_name("chord-diagram-regular-font"); + if (!font_obj) { + LOG_DEBUG("out_pdf_fnt_obj_get_by_name failed."); + return false; + } + name_width = pdfioContentTextMeasure(font_obj, diagram->name, field_width*2.0); + centered_x = (width - name_width) / 2; + if (!pdfioContentSetTextFont(stream, "chord-diagram-regular-font", field_width*2.0)) { + fprintf(stderr, "pdfioContentSetTextFont failed.\n"); + return false; + } + if (!text_show(stream, diagram->name, x+centered_x, y_above_diagram + 7.0)) { + fprintf(stderr, "text_show failed.\n"); + return false; + } + return true; +} + +struct KeyboardDiagram * +keyboard_diagram_new(void) +{ + struct KeyboardDiagram *d = emalloc(sizeof(struct KeyboardDiagram)); + d->name = NULL; + memset(d->keys, -2, sizeof(d->keys)); + return d; +} + +static struct KeyboardDiagram * +keyboard_diagram_copy_all_but_name(struct KeyboardDiagram *diagram) +{ + struct KeyboardDiagram *copy = keyboard_diagram_new(); + memcpy(copy->keys, diagram->keys, 24); + return copy; +} + +static void +keyboard_diagram_free(struct KeyboardDiagram *d) +{ + free(d->name); + free(d); +} + +struct ChordDiagram * +chord_diagram_new() +{ + struct ChordDiagram *d = emalloc(sizeof(struct ChordDiagram)); + d->show = true; + d->color = cho_rgbcolor_new(20, 20, 20); + return d; +} + +void +chord_diagram_free(struct ChordDiagram *d) +{ + free(d->color); + if (d->is_string_instrument) { + string_diagram_free(d->u.sd); + } else { + keyboard_diagram_free(d->u.kd); + } + free(d); +} + +static struct ChordDiagram * +chord_diagram_copy_all_but_name(struct ChordDiagram *diagram) +{ + struct ChordDiagram *copy = emalloc(sizeof(struct ChordDiagram)); + copy->show = diagram->show; + copy->color = cho_color_copy(diagram->color); + copy->is_string_instrument = diagram->is_string_instrument; + if (diagram->is_string_instrument) { + copy->u.sd = string_diagram_copy_all_but_name(diagram->u.sd); + } else { + copy->u.kd = keyboard_diagram_copy_all_but_name(diagram->u.kd); + } + return copy; +} + +enum ChordDiagramContent +chord_diagram_duplicate( + struct ChordDiagram *diagram, + struct ChordDiagram **custom_diagrams, + int custom_diagrams_len, + const char *name, + const char *chord_to_copy, + enum Instrument instrument +) +{ + int d; + for (d = custom_diagrams_len-1; d >= 0; d--) { + if (custom_diagrams[d]->is_string_instrument) { + if (!strcmp(custom_diagrams[d]->u.sd->name, chord_to_copy)) { + diagram->is_string_instrument = true; + diagram->u.sd = string_diagram_copy_all_but_name(custom_diagrams[d]->u.sd); + diagram->u.sd->name = strdup(name); + return CDC_STRING; + } + } else { + if (!strcmp(custom_diagrams[d]->u.kd->name, chord_to_copy)) { + diagram->is_string_instrument = false; + diagram->u.kd = keyboard_diagram_copy_all_but_name(custom_diagrams[d]->u.kd); + diagram->u.kd->name = strdup(name); + return CDC_KEYBOARD; + } + } + } + switch (instrument) { + case INS_GUITAR: { + size_t i; + for (i = 0; i<LENGTH(guitar_diagrams); i++) { + if (!strcmp(guitar_diagrams[i].name, chord_to_copy)) { + diagram->is_string_instrument = true; + diagram->u.sd = string_diagram_copy_all_but_name(&guitar_diagrams[i]); + diagram->u.sd->name = strdup(name); + return CDC_STRING; + } + } + break; + } + case INS_KEYBOARD: { + break; + } + case INS_MANDOLIN: { + break; + } + case INS_UKULELE: { + break; + } + } + return -1; +} + +void +chord_diagrams_free(struct ChordDiagram **diagrams) +{ + if (!diagrams) { + return; + } + struct ChordDiagram **d; + for (d = diagrams; *d; d++) { + chord_diagram_free(*d); + } + free(diagrams); +} + +#ifdef DEBUG +void +debug_chord_diagram_print(struct ChordDiagram *diagram) +{ + int i; + printf("---- CHORD DIAGRAM BEGIN ----\n"); + printf("show: %s\n", diagram->show ? "true" : "false"); + + char str[8]; + str[7] = 0; + sprintf((char *)&str, "#%02X%02X%02X", diagram->color->red, diagram->color->green, diagram->color->blue); + + printf("color: %s\n", str); + + if (diagram->is_string_instrument) { + printf("name: %s\n", diagram->u.sd->name); + printf("base-fret: %d\n", diagram->u.sd->base_fret); + printf("frets: "); + for (i = 0; i<12; i++) { + printf("%d ", diagram->u.sd->frets[i]); + } + printf("\n"); + printf("fingers: "); + for (i = 0; i<12; i++) { + printf("%d ", diagram->u.sd->fingers[i]); + } + printf("\n"); + } else { + printf("name: %s\n", diagram->u.kd->name); + printf("keys: "); + for (i = 0; i<24; i++) { + printf("%d ", diagram->u.kd->keys[i]); + } + printf("\n"); + } + printf("---- CHORD DIAGRAM END ------\n"); +} +#endif /* DEBUG */ + +struct ChordDiagram ** +chord_diagrams_create( + struct Config *config, + struct ChoChord ***chords, + struct ChordDiagram **custom_diagrams +) +{ + struct ChordDiagram **diagrams = NULL; + struct ChordDiagram **cd; + struct ChoChord **c; + int d = 0; + size_t i; + switch (config->output->diagram->instrument) { + case INS_GUITAR: + for (c = *chords; *c; c++) { + for (cd = custom_diagrams; *cd; cd++) { + if ( + (*cd)->is_string_instrument && + string_diagram_fret_count((*cd)->u.sd) == 6 && + !strcmp((*c)->name, (*cd)->u.sd->name) + ) { + diagrams = erealloc(diagrams, (d+1) * sizeof(struct ChordDiagram *)); + diagrams[d] = chord_diagram_copy_all_but_name(*cd); + diagrams[d]->u.sd->name = cho_chord_name_generate(*c); + d++; + goto NEXT_CHORD; + } + } + for (i = 0; i<LENGTH(guitar_diagrams); i++) { + if (!strcmp((*c)->name, guitar_diagrams[i].name)) { + diagrams = erealloc(diagrams, (d+1) * sizeof(struct ChordDiagram *)); + diagrams[d] = chord_diagram_new(); + diagrams[d]->is_string_instrument = true; + diagrams[d]->u.sd = string_diagram_copy_all_but_name(&guitar_diagrams[i]); + diagrams[d]->u.sd->name = cho_chord_name_generate(*c); + d++; + break; + } + } + NEXT_CHORD: ; + } + break; + case INS_KEYBOARD: + break; + case INS_MANDOLIN: + break; + case INS_UKULELE: + break; + default: + util_log(LOG_ERR, "Invalid Instrument enum value '%d'.", config->output->diagram->instrument); + } + diagrams = erealloc(diagrams, (d+1) * sizeof(struct ChordDiagram *)); + diagrams[d] = NULL; + return diagrams; +} + +bool +chord_diagram_draw( + pdfio_stream_t *stream, + struct ChordDiagram *diagram, + double x, + double y, + double width +) +{ + if (diagram->is_string_instrument) { + if (!string_diagram_draw(stream, diagram->u.sd, x, y, width)) { + LOG_DEBUG("draw_string_chord_diagram failed."); + return false; + } + } else { + util_log(LOG_TODO, "draw_keyboard_chord_diagram()"); + } + return true; +} diff --git a/src/chord_diagram.h b/src/chord_diagram.h @@ -0,0 +1,26 @@ +#include <pdfio.h> +#include "types.h" + +enum TextRendering { + FILL, + STROKE, + FILL_AND_STROKE +}; + +enum Direction { + HORIZONTAL, + VERTICAL +}; + +struct ChordDiagram *chord_diagram_new(); +void chord_diagram_free(struct ChordDiagram *d); +bool chord_diagram_draw(pdfio_stream_t *stream, struct ChordDiagram *diagram, double x, double y, double width); +enum ChordDiagramContent chord_diagram_duplicate(struct ChordDiagram *diagram, struct ChordDiagram **custom_diagrams, int custom_diagrams_len, const char *name, const char *chord_to_copy, enum Instrument instrument); + +struct StringDiagram *string_diagram_new(void); +struct KeyboardDiagram *keyboard_diagram_new(void); + +void chord_diagrams_free(struct ChordDiagram **diagrams); +struct ChordDiagram **chord_diagrams_create(struct Config *config, struct ChoChord ***chords, struct ChordDiagram **custom_diagram); + +void debug_chord_diagram_print(struct ChordDiagram *diagram); diff --git a/src/chordpro.c b/src/chordpro.c @@ -0,0 +1,5470 @@ +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <stdarg.h> +#include <unistd.h> +#include <string.h> +#include <strings.h> +#include <ctype.h> +#include <errno.h> +#include <limits.h> +#include "types.h" +#include "chordpro.h" +#include "util.h" +#include "chord_diagram.h" +#include "config.h" + +static const char *font_families[] = { "normal", "sans", "serif", "monospace" }; +static const char *font_styles[] = { "normal", "oblique", "italic" }; +static const char *font_weights[] = { "normal", "bold" }; +static const char *line_styles[] = { "single", "double", "none" }; +static const char *chord_qualifiers[] = { "m", "", "+", "°" }; +static const char *instruments[] = { "guitar", "keyboard", "mandolin", "ukulele" }; + +static const char *state_enums[] = { + "STATE_LYRICS", + "STATE_BACKSLASH", + "STATE_DIRECTIVE_NAME", + "STATE_DIRECTIVE_VALUE", + "STATE_CHORD", + "STATE_ANNOTATION", + "STATE_TAB", + "STATE_MARKUP_TAG", + "STATE_MARKUP_TAG_START", + "STATE_MARKUP_TAG_END", + "STATE_MARKUP_ATTR_NAME", + "STATE_MARKUP_ATTR_VALUE", + "STATE_COMMENT" +}; + +struct StyleProperty default_style_properties[] = { + // TODO: label and footer are missing + { TT_CHORD, SPT_FONT, { .font_name = NULL } }, + { TT_CHORD, SPT_SIZE, { .font_size = EMPTY_DOUBLE } }, + { TT_CHORD, SPT_COLOR, { .foreground_color = NULL } }, + { TT_CHORUS, SPT_FONT, { .font_name = NULL } }, + { TT_CHORUS, SPT_SIZE, { .font_size = EMPTY_DOUBLE } }, + { TT_CHORUS, SPT_COLOR, { .foreground_color = NULL } }, + { TT_GRID, SPT_FONT, { .font_name = NULL } }, + { TT_GRID, SPT_SIZE, { .font_size = EMPTY_DOUBLE } }, + { TT_GRID, SPT_COLOR, { .foreground_color = NULL } }, + { TT_TAB, SPT_FONT, { .font_name = NULL } }, + { TT_TAB, SPT_SIZE, { .font_size = EMPTY_DOUBLE } }, + { TT_TAB, SPT_COLOR, { .foreground_color = NULL } }, + { TT_TEXT, SPT_FONT, { .font_name = NULL } }, + { TT_TEXT, SPT_SIZE, { .font_size = EMPTY_DOUBLE } }, + { TT_TEXT, SPT_COLOR, { .foreground_color = NULL } }, + { TT_TITLE, SPT_FONT, { .font_name = NULL } }, + { TT_TITLE, SPT_SIZE, { .font_size = EMPTY_DOUBLE } }, + { TT_TITLE, SPT_COLOR, { .foreground_color = NULL } }, + { TT_TOC, SPT_FONT, { .font_name = NULL } }, + { TT_TOC, SPT_SIZE, { .font_size = EMPTY_DOUBLE } }, + { TT_TOC, SPT_COLOR, { .foreground_color = NULL } }, +}; + +static const char *chord_extensions_major[] = { + "2", "3", "4", "5", "6", "69", "7", "7-5", + "7#5", "7#9", "7#9#5", "7#9b5", "7#9#11", + "7b5", "7b9", "7b9#5", "7b9#9", "7b9#11", "7b9b13", "7b9b5", "7b9sus", "7b13", "7b13sus", + "7-9", "7-9#11", "7-9#5", "7-9#9", "7-9-13", "7-9-5", "7-9sus", + "711", + "7#11", + "7-13", "7-13sus", + "7sus", "7susadd3", + "7+", + "7alt", + "9", + "9+", + "9#5", + "9b5", + "9-5", + "9sus", + "9add6", + "maj7", "maj711", "maj7#11", "maj13", "maj7#5", "maj7sus2", "maj7sus4", + "^7", "^711", "^7#11", "^7#5", "^7sus2", "^7sus4", + "maj9", "maj911", + "^9", "^911", + "^13", + "^9#11", + "11", + "911", + "9#11", + "13", + "13#11", + "13#9", + "13b9", + "alt", + "add2", "add4", "add9", + "sus2", "sus4", "sus9", + "6sus2", "6sus4", + "7sus2", "7sus4", + "13sus2", "13sus4", + NULL +}; + +static const char *chord_extensions_minor[] = { + // INFO: https://www.chordpro.org/beta/chordpro-chords/#extensions-for-minor-chords + "#5", + "#5", + "m11", + "-11", + "m6", + "-6", + "m69", + "-69", + "m7b5", + "-7b5", + "m7-5", + "-7-5", + "mmaj7", + "-maj7", + "mmaj9", + "-maj9", + "m9maj7", + "-9maj7", + "m9^7", + "-9^7", + "madd9", + "-add9", + "mb6", + "-b6", + "m#7", + "-#7", + "msus4", "msus9", + "-sus4", "-sus9", + "m7sus4", + "-7sus4", + // INFO: custom + "m7", + "m9", + "m", + NULL +}; + +static bool g_show_info_logs = false; +static enum TextType g_current_ttype = TT_TEXT; +static enum TextType g_prev_ttype = TT_TEXT; +static struct Config *g_config = NULL; +static const char *g_chordpro_filepath = NULL; +static int *g_transpose_history = NULL; +static int *g_transpose = NULL; +static size_t g_line_number = 1; +struct ChoImage **g_image_assets = NULL; +static int g_ia = 0; + +#ifdef DEBUG + +static const char *alignment_enums[] = { + "A_LEFT", + "A_CENTER", + "A_RIGHT" +}; + +static const char *anchor_enums[] = { + "AN_PAPER", + "AN_PAGE", + "AN_COLUMN", + "AN_LINE", + "AN_FLOAT" +}; + +static const char *chord_qualifier_enums[] = { + "CQ_MIN", + "CQ_MAJ", + "CQ_AUG", + "CQ_DIM" +}; + +static const char *directive_type_enums[] = { + "DT_ENVIRONMENT", + "DT_METADATA", + "DT_FORMATTING", + "DT_IMAGE", + "DT_PREAMBLE", + "DT_FONT", + "DT_CHORD", + "DT_OUTPUT", + "DT_EXTENSION", + "DT_CUSTOM" +}; + +static const char *section_type_enums[] = { + "ST_NEWSONG", + "ST_CHORUS", + "ST_VERSE", + "ST_BRIDGE", + "ST_TAB", + "ST_GRID", + "ST_CUSTOM" +}; + +static const char *position_enums[] = { + "POS_START", + "POS_END", + "POS_NO" +}; + +static const char *text_type_enums[] = { + "TT_CHORD", + "TT_ANNOT", + "TT_CHORUS", + "TT_FOOTER", + "TT_GRID", + "TT_TAB", + "TT_TOC", + "TT_TOC_TITLE", + "TT_TEXT", + "TT_TITLE", + "TT_SUBTITLE", + "TT_LABEL", + "TT_COMMENT", + "TT_COMMENT_ITALIC", + "TT_COMMENT_BOX" +}; + +static const char *style_property_type_enums[] = { + "SPT_FONT", + "SPT_SIZE", + "SPT_COLOR" +}; + +static void +cho_debug_chord_print(struct ChoChord *chord) +{ + printf("---- BEGIN CHORD ----\n"); + printf("is_canonical: %d\n", chord->is_canonical); + printf("name: '%s'\n", chord->name); + if (chord->root) { + printf("root: '%s'\n", chord->root); + } else { + printf("root: 'NULL'\n"); + } + printf("qual: '%s'\n", chord_qualifier_enums[chord->qual]); + if (chord->ext) { + printf("ext: '%s'\n", chord->ext); + } else { + printf("ext: 'NULL'\n"); + } + if (chord->bass) { + printf("bass: '%s'\n", chord->bass); + } else { + printf("bass: 'NULL'\n"); + } + printf("---- END CHORD ------\n"); +} + +#endif /* DEBUG */ + +void +cho_log_enable_info_logs(void) +{ + g_show_info_logs = true; +} + +#if COLOR == 1 +static void +cho_log(enum LogLevel level, const char *msg, ...) +{ + if (level == LOG_INFO && !g_show_info_logs) { + return; + } + va_list va; + va_start(va, msg); + const char *log_level; + const char *color; + switch (level) { + case LOG_INFO: + log_level = "INFO"; + color = COLOR_BOLD_WHITE; + break; + case LOG_WARN: + log_level = "WARN"; + color = COLOR_BOLD_ORANGE; + break; + case LOG_ERR: + log_level = " ERR"; + color = COLOR_BOLD_RED; + break; + case LOG_TODO: + log_level = "TODO"; + color = COLOR_BOLD_BLUE; + break; + } + if (isatty(2)) { + if (g_chordpro_filepath) { + fprintf(stderr, COLOR_BOLD_WHITE"%s:%ld:"COLOR_RESET" %s%s"COLOR_RESET": ", + g_chordpro_filepath, g_line_number, color, log_level); + vfprintf(stderr, msg, va); + fprintf(stderr, "\n"); + } else { + fprintf(stderr, "line "COLOR_BOLD_WHITE"%ld:"COLOR_RESET" %s%s"COLOR_RESET": ", + g_line_number, color, log_level); + vfprintf(stderr, msg, va); + fprintf(stderr, "\n"); + } + } else { + if (g_chordpro_filepath) { + fprintf(stderr, "%s:%ld: %s: ", g_chordpro_filepath, g_line_number, + log_level); + vfprintf(stderr, msg, va); + fprintf(stderr, "\n"); + } else { + fprintf(stderr, "line %ld: %s: ", g_line_number, log_level); + vfprintf(stderr, msg, va); + fprintf(stderr, "\n"); + } + } +} +#else +static void +cho_log(enum LogLevel level, const char *msg, ...) +{ + if (level == LOG_INFO && !g_show_info_logs) { + return; + } + va_list va; + va_start(va, msg); + const char *log_level; + switch (level) { + case LOG_INFO: + log_level = "INFO"; + break; + case LOG_WARN: + log_level = "WARN"; + break; + case LOG_ERR: + log_level = " ERR"; + break; + case LOG_TODO: + log_level = "TODO"; + break; + } + if (g_chordpro_filepath) { + fprintf(stderr, "%s:%ld: %s: ", g_chordpro_filepath, g_line_number, + log_level); + vfprintf(stderr, msg, va); + fprintf(stderr, "\n"); + } else { + fprintf(stderr, "line %ld: %s: ", g_line_number, log_level); + vfprintf(stderr, msg, va); + fprintf(stderr, "\n"); + } +} +#endif /* COLOR */ + +static inline bool +is_whitespace(char c) +{ + if (c == '\t' || c == ' ') { + return true; + } + return false; +} + +struct RGBColor * +cho_rgbcolor_new(uint8_t red, uint8_t green, uint8_t blue) +{ + struct RGBColor *color = emalloc(sizeof(struct RGBColor)); + color->red = red; + color->green = green; + color->blue = blue; + return color; +} + +static struct RGBColor * +cho_rgbcolor_copy(struct RGBColor *color) +{ + struct RGBColor *copy = emalloc(sizeof(struct RGBColor)); + copy->red = color->red; + copy->green = color->green; + copy->blue = color->blue; + return copy; +} + +static const char * +cho_rgbcolor_to_string(struct RGBColor *color) +{ + static char str[8]; + str[7] = 0; + sprintf((char *)&str, "#%02X%02X%02X", color->red, color->green, color->blue); + return (const char *)&str; +} + +static struct RGBColor * +cho_rgbcolor_parse(const char *str) +{ + struct RGBColor *color = emalloc(sizeof(struct RGBColor)); + size_t len = strlen(str); + char tmp[3]; + long primary_color; + if (len == 7) { + tmp[2] = 0; + if (str[1] == '0' && str[2] == '0') { + color->red = 0; + } else { + tmp[0] = str[1]; + tmp[1] = str[2]; + primary_color = strtol((char *)&tmp, NULL, 16); + if (primary_color == 0) { + cho_log(LOG_ERR, "Invalid primary color in rgb color."); + free(color); + return NULL; + } else { + color->red = primary_color; + } + } + if (str[3] == '0' && str[4] == '0') { + color->green = 0; + } else { + tmp[0] = str[3]; + tmp[1] = str[4]; + primary_color = strtol((char *)&tmp, NULL, 16); + if (primary_color == 0) { + cho_log(LOG_ERR, "Invalid primary color in rgb color."); + free(color); + return NULL; + } else { + color->green = primary_color; + } + } + if (str[5] == '0' && str[6] == '0') { + color->blue = 0; + } else { + tmp[0] = str[5]; + tmp[1] = str[6]; + primary_color = strtol((char *)&tmp, NULL, 16); + if (primary_color == 0) { + cho_log(LOG_ERR, "Invalid primary color in rgb color."); + free(color); + return NULL; + } else { + color->blue = primary_color; + } + } + } else if (len == 4) { + tmp[2] = 0; + if (str[1] == '0') { + color->red = 0; + } else { + tmp[0] = str[1]; + tmp[1] = str[1]; + primary_color = strtol((char *)&tmp, NULL, 16); + if (primary_color == 0) { + cho_log(LOG_ERR, "Invalid primary color in rgb color."); + free(color); + return NULL; + } else { + color->red = primary_color; + } + } + if (str[2] == '0') { + color->green = 0; + } else { + tmp[0] = str[2]; + tmp[1] = str[2]; + primary_color = strtol((char *)&tmp, NULL, 16); + if (primary_color == 0) { + cho_log(LOG_ERR, "Invalid primary color in rgb color."); + free(color); + return NULL; + } else { + color->green = primary_color; + } + } + if (str[3] == '0') { + color->blue = 0; + } else { + tmp[0] = str[3]; + tmp[1] = str[3]; + primary_color = strtol((char *)&tmp, NULL, 16); + if (primary_color == 0) { + cho_log(LOG_ERR, "Invalid primary color in rgb color."); + free(color); + return NULL; + } else { + color->blue = primary_color; + } + } + } else { + cho_log(LOG_ERR, "Invalid rgb color."); + free(color); + return NULL; + } + return color; +} + +struct RGBColor * +cho_color_parse(const char *str) +{ + struct RGBColor *color = emalloc(sizeof(struct RGBColor)); + if (str[0] == '#') { + struct RGBColor *rgb_color = cho_rgbcolor_parse(str); + if (rgb_color) { + free(color); + color = rgb_color; + } else { + free(color); + LOG_DEBUG("cho_rgbcolor_parse failed."); + return NULL; + } + } else if (!strcmp(str, "red")) { + color->red = 255; + color->green = 48; + color->blue = 48; + } else if (!strcmp(str, "green")) { + color->red = 0; + color->green = 126; + color->blue = 0; + } else if (!strcmp(str, "blue")) { + color->red = 0; + color->green = 0; + color->blue = 255; + } else if (!strcmp(str, "yellow")) { + color->red = 255; + color->green = 255; + color->blue = 0; + } else if (!strcmp(str, "magenta")) { + color->red = 255; + color->green = 48; + color->blue = 255; + } else if (!strcmp(str, "cyan")) { + color->red = 82; + color->green = 255; + color->blue = 255; + } else if (!strcmp(str, "white")) { + color->red = 255; + color->green = 255; + color->blue = 255; + } else if (!strcmp(str, "grey")) { + color->red = 191; + color->green = 191; + color->blue = 191; + } else if (!strcmp(str, "black")) { + color->red = 0; + color->green = 0; + color->blue = 0; + } else { + cho_log(LOG_ERR, "Invalid color value '%s'.", str); + free(color); + return NULL; + } + return color; +} + +inline struct RGBColor * +cho_color_copy(struct RGBColor *color) +{ + return cho_rgbcolor_copy(color); +} + +static struct Font * +cho_font_new(void) +{ + struct Font *font = emalloc(sizeof(struct Font)); + font->name = NULL; + font->family = FF_NORMAL; + font->style = FS_ROMAN; + font->weight = FW_REGULAR; + font->size = DEFAULT_FONT_SIZE; + return font; +} + +struct Font * +cho_font_copy(struct Font *font) +{ + struct Font *copy = emalloc(sizeof(struct Font)); + if (font->name) + copy->name = strdup(font->name); + else + copy->name = NULL; + copy->family = font->family; + copy->style = font->style; + copy->weight = font->weight; + copy->size = font->size; + return copy; +} + +void +cho_font_free(struct Font *font) +{ + if (!font) { + return; + } + free(font->name); + free(font); +} + +void +cho_fonts_free(struct Font **fonts) +{ + if (!fonts) { + return; + } + struct Font **f; + for (f = fonts; *f; f++) { + cho_font_free(*f); + } + free(fonts); +} + +enum FontFamily +cho_font_family_parse(const char *str) +{ + if (!strcmp(str, font_families[FF_SANS])) { + return FF_SANS; + } else if (!strcmp(str, font_families[FF_SERIF])) { + return FF_SERIF; + } else if (!strcmp(str, font_families[FF_MONOSPACE])) { + return FF_MONOSPACE; + } else if (!strcmp(str, font_families[FF_NORMAL])) { + return FF_NORMAL; + } else { + return -1; + } +} + +const char * +cho_font_family_to_config_string(enum FontFamily font_family) +{ + return font_families[font_family]; +} + +enum FontStyle +cho_font_style_parse(const char *str) +{ + if (!strcmp(str, font_styles[FS_ITALIC])) { + return FS_ITALIC; + } else if (!strcmp(str, font_styles[FS_OBLIQUE])) { + return FS_OBLIQUE; + } else if (!strcmp(str, font_styles[FS_ROMAN])) { + return FS_ROMAN; + } else { + return -1; + } +} + +const char * +cho_font_style_to_config_string(enum FontStyle style) +{ + return font_styles[style]; +} + +enum FontWeight +cho_font_weight_parse(const char *str) +{ + if (!strcmp(str, "bold")) { + return FW_BOLD; + } else if (!strcmp(str, "normal")) { + return FW_REGULAR; + } else { + return -1; + } +} + +const char * +cho_font_weight_to_config_string(enum FontWeight weight) +{ + return font_weights[weight]; +} + +static void +cho_font_print_as_toml(struct Font *font, const char *section) +{ + printf("[output.styles.%s.font]\n", section); + printf("name = \"%s\"\n", font->name); + printf("family = \"%s\"\n", cho_font_family_to_config_string(font->family)); + printf("style = \"%s\"\n", cho_font_style_to_config_string(font->style)); + printf("weight = \"%s\"\n", cho_font_weight_to_config_string(font->weight)); + printf("size = %.1f\n\n", font->size); +} + +void +cho_font_print(struct Font *font) +{ + printf("---- BEGIN FONT ----\n"); + if (font->name) + printf("font name: %s\n", font->name); + else + printf("font name: NULL\n"); + printf("font family: %s\n", cho_font_family_to_config_string(font->family)); + printf("font style: %s\n", cho_font_style_to_config_string(font->style)); + printf("font weight: %s\n", cho_font_weight_to_config_string(font->weight)); + printf("font size: %f\n", font->size); + printf("---- END FONT ------\n"); +} + +enum LineStyle +cho_linestyle_parse(const char *str) +{ + if (!strcmp(str, "single")) { + return LS_SINGLE; + } else if (!strcmp(str, "double")) { + return LS_DOUBLE; + } else if (!strcmp(str, "none")) { + return LS_NONE; + } else { + return -1; + } +} + +const char * +cho_linestyle_to_config_string(enum LineStyle linestyle) +{ + return line_styles[linestyle]; +} + +#ifdef DEBUG + +static void +cho_debug_the_default_style_properties(void) +{ + unsigned int i; + for (i = 0; i<LENGTH(default_style_properties); i++) { + printf( + "%s %s ", + text_type_enums[default_style_properties[i].ttype], + style_property_type_enums[default_style_properties[i].type] + ); + switch (default_style_properties[i].type) { + case SPT_FONT: + if (default_style_properties[i].u.font_name) + printf("%s\n", default_style_properties[i].u.font_name); + else + printf("NULL\n"); + break; + case SPT_SIZE: + printf("%.1f\n", default_style_properties[i].u.font_size); + break; + case SPT_COLOR: + if (default_style_properties[i].u.foreground_color) + printf("%s\n", cho_rgbcolor_to_string(default_style_properties[i].u.foreground_color)); + else + printf("NULL\n"); + break; + default: + printf("Invalid StylePropertyType value '%d'.\n", default_style_properties[i].type); + } + } +} + +void +cho_debug_style_print(struct ChoStyle *style) +{ + printf("---- BEGIN STYLE ----\n"); + cho_font_print(style->font); + printf("foreground_color: %s\n", cho_rgbcolor_to_string(style->foreground_color)); + printf("background_color: %s\n", cho_rgbcolor_to_string(style->background_color)); + printf("underline_style: %s\n", cho_linestyle_to_config_string(style->underline_style)); + printf("underline_color: %s\n", cho_rgbcolor_to_string(style->underline_color)); + printf("overline_style: %s\n", cho_linestyle_to_config_string(style->overline_style)); + printf("overline_color: %s\n", cho_rgbcolor_to_string(style->overline_color)); + printf("strikethrough: %d\n", style->strikethrough); + printf("strikethrough_color: %s\n", cho_rgbcolor_to_string(style->strikethrough_color)); + printf("boxed: %d\n", style->boxed); + printf("boxed_color: %s\n", cho_rgbcolor_to_string(style->boxed_color)); + printf("rise: %f\n", style->rise); + if (style->href) + printf("href: %s\n", style->href); + else + printf("href: NULL\n"); + printf("---- END STYLE ------\n\n"); +} + +#endif /* DEBUG */ + +static bool +cho_style_property_apply_default(enum TextType current_ttype, enum StylePropertyType ptype, struct ChoStyle *style) +{ + unsigned int i; + for (i = 0; i<LENGTH(default_style_properties); i++) { + if ( + default_style_properties[i].ttype == current_ttype && + default_style_properties[i].type == ptype + ) { + switch (ptype) { + case SPT_FONT: + if (default_style_properties[i].u.font_name) { + free(style->font->name); + style->font->name = strdup(default_style_properties[i].u.font_name); + return true; + } else { + return false; + } + break; + case SPT_SIZE: + if (default_style_properties[i].u.font_size != EMPTY_DOUBLE) { + style->font->size = default_style_properties[i].u.font_size; + return true; + } else { + return false; + } + break; + case SPT_COLOR: + if (default_style_properties[i].u.foreground_color) { + free(style->foreground_color); + style->foreground_color = cho_rgbcolor_copy(default_style_properties[i].u.foreground_color); + return true; + } else { + return false; + } + break; + default: + cho_log(LOG_WARN, "Invalid style property type '%d'.", ptype); + return false; + } + } + } + return false; +} + +static void +cho_style_apply_default(enum TextType current_ttype, struct ChoStyle *style) +{ + if (current_ttype == TT_CHORUS) { + if (!cho_style_property_apply_default(TT_CHORUS, SPT_FONT, style)) { + cho_style_property_apply_default(TT_TEXT, SPT_FONT, style); + } + if (!cho_style_property_apply_default(TT_CHORUS, SPT_SIZE, style)) { + cho_style_property_apply_default(TT_TEXT, SPT_SIZE, style); + } + if (!cho_style_property_apply_default(TT_CHORUS, SPT_COLOR, style)) { + cho_style_property_apply_default(TT_TEXT, SPT_COLOR, style); + } + } else { + cho_style_property_apply_default(current_ttype, SPT_FONT, style); + cho_style_property_apply_default(current_ttype, SPT_SIZE, style); + cho_style_property_apply_default(current_ttype, SPT_COLOR, style); + } +} + +struct ChoStyle * +cho_style_new(void) +{ + struct ChoStyle *style = emalloc(sizeof(struct ChoStyle)); + style->font = cho_font_new(); + style->foreground_color = cho_rgbcolor_new(20, 20, 20); + style->background_color = cho_rgbcolor_new(255, 255, 255); + style->underline_style = LS_NONE; + style->underline_color = cho_rgbcolor_new(20, 20, 20); + style->overline_style = LS_NONE; + style->overline_color = cho_rgbcolor_new(20, 20, 20); + style->strikethrough = false; + style->strikethrough_color = cho_rgbcolor_new(20, 20, 20); + style->boxed = false; + style->boxed_color = cho_rgbcolor_new(20, 20, 20); + style->rise = 0.0; + style->href = NULL; + return style; +} + +struct ChoStyle * +cho_style_copy(struct ChoStyle *style) +{ + struct ChoStyle *copy = emalloc(sizeof(struct ChoStyle)); + copy->font = cho_font_copy(style->font); + copy->foreground_color = cho_rgbcolor_copy(style->foreground_color); + copy->background_color = cho_rgbcolor_copy(style->background_color); + copy->underline_style = style->underline_style; + copy->underline_color = cho_rgbcolor_copy(style->underline_color); + copy->overline_style = style->overline_style; + copy->overline_color = cho_rgbcolor_copy(style->overline_color); + copy->strikethrough = style->strikethrough; + copy->strikethrough_color = cho_rgbcolor_copy(style->strikethrough_color); + copy->boxed = style->boxed; + copy->boxed_color = cho_rgbcolor_copy(style->boxed_color); + copy->rise = style->rise; + copy->href = style->href ? strdup(style->href) : NULL; + return copy; +} + +static void +cho_style_complement( + struct ChoStyle *old, + struct ChoStyle *new, + struct ChoStylePresence *presence +) +{ + if (presence->font.name) { + free(old->font->name); + old->font->name = strdup(new->font->name); + } + if (presence->font.family) { + old->font->family = new->font->family; + } + if (presence->font.style) { + old->font->style = new->font->style; + } + if (presence->font.weight) { + old->font->weight = new->font->weight; + } + if (presence->font.size) { + old->font->size = new->font->size; + } + if (presence->foreground_color) { + free(old->foreground_color); + old->foreground_color = cho_rgbcolor_copy(new->foreground_color); + } + if (presence->background_color) { + free(old->background_color); + old->background_color = cho_rgbcolor_copy(new->background_color); + } + if (presence->underline_style) { + old->underline_style = new->underline_style; + } + if (presence->underline_color) { + free(old->underline_color); + old->underline_color = cho_rgbcolor_copy(new->underline_color); + } + if (presence->overline_style) { + old->overline_style = new->overline_style; + } + if (presence->overline_color) { + free(old->overline_color); + old->overline_color = cho_rgbcolor_copy(new->overline_color); + } + if (presence->strikethrough) { + old->strikethrough = new->strikethrough; + } + if (presence->strikethrough_color) { + free(old->strikethrough_color); + old->strikethrough_color = cho_rgbcolor_copy(new->strikethrough_color); + } + if (presence->boxed) { + old->boxed = new->boxed; + } + if (presence->boxed_color) { + free(old->boxed_color); + old->boxed_color = cho_rgbcolor_copy(new->boxed_color); + } + if (presence->rise) { + old->rise = new->rise; + } + if (presence->href) { + free(old->href); + old->href = strdup(new->href); + } +} + +static struct ChoStyle * +cho_style_new_from_config(enum TextType ttype) +{ + return cho_style_copy(g_config->output->styles[ttype]); +} + +static struct ChoStyle * +cho_style_new_default(void) +{ + struct ChoStyle *style = cho_style_new_from_config(g_current_ttype); + if (!style) { + LOG_DEBUG("cho_style_new_from_config failed."); + return NULL; + } + cho_style_apply_default(g_current_ttype, style); + return style; +} + +void +cho_style_free(struct ChoStyle *style) +{ + if (!style) { + return; + } + cho_font_free(style->font); + free(style->foreground_color); + free(style->background_color); + free(style->underline_color); + free(style->overline_color); + free(style->strikethrough_color); + free(style->boxed_color); + free(style->href); + free(style); +} + +static struct Font * +cho_style_font_desc_parse(const char *str, struct ChoStylePresence *presence) +{ + if (strlen(str) == 0) { + return NULL; + } + struct Font *font = cho_font_new(); + double size = -1.0; + char **words = emalloc(sizeof(char *)); + int w = 0; + words[w] = NULL; + int k = 0; + int i; + for (i = 0; str[i]; i++) { + if (str[i] == ' ') { + words[w] = erealloc(words[w], (k+1) * sizeof(char)); + words[w][k] = 0; + if (strlen(words[w]) == 0) { + free(words[w]); + } else { + w++; + } + k = 0; + words = erealloc(words, (w+1) * sizeof(char *)); + words[w] = NULL; + } else { + words[w] = erealloc(words[w], (k+1) * sizeof(char)); + words[w][k] = str[i]; + k++; + } + } + words[w] = erealloc(words[w], (k+1) * sizeof(char)); + words[w][k] = 0; + w++; + words = erealloc(words, (w+1) * sizeof(char *)); + words[w] = NULL; + int stop_at = EMPTY_INT; + for (w = 0; words[w]; w++) { + if (strcasecmp(words[w], "italic") == 0) { + font->style = FS_ITALIC; + presence->font.style = true; + if (stop_at == EMPTY_INT) { + stop_at = w; + } + } else if (strcasecmp(words[w], "bold") == 0) { + font->weight = FW_BOLD; + presence->font.weight = true; + if (stop_at == EMPTY_INT) { + stop_at = w; + } + } else if (strcasecmp(words[w], "oblique") == 0) { + font->style = FS_OBLIQUE; + presence->font.style = true; + if (stop_at == EMPTY_INT) { + stop_at = w; + } + // TODO: Is that smart? + } else if (strcasecmp(words[w], "regular") == 0) { + font->weight = FW_REGULAR; + presence->font.weight = true; + if (stop_at == EMPTY_INT) { + stop_at = w; + } + // TODO: Is that smart? + } else if (strcasecmp(words[w], "normal") == 0) { + font->style = FS_ROMAN; + presence->font.style = true; + if (stop_at == EMPTY_INT) { + stop_at = w; + } + /* Commented because the family name sometimes contains 'sans' or 'serif' */ + /* } else if (strcasecmp(words[w], "sans") == 0) { + font->family = FF_SANS; + if (stop_at == EMPTY_INT) + stop_at = w; + } else if (strcasecmp(words[w], "serif") == 0) { + font->family = FF_SERIF; + if (stop_at == EMPTY_INT) + stop_at = w; */ + } else if (strcasecmp(words[w], "monospace") == 0) { + font->family = FF_MONOSPACE; + presence->font.family = true; + if (stop_at == EMPTY_INT) { + stop_at = w; + } + } else { + size = strtod(words[w], NULL); + if (size == 0.0) { + continue; + } + font->size = size; + presence->font.size = true; + if (stop_at == EMPTY_INT) { + stop_at = w; + } + } + } + if (stop_at == EMPTY_INT) { + stop_at = w; + } + if (stop_at > 0) { + int n = 0; + for (i = 0; i<stop_at; i++) { + for (k = 0; words[i][k]; n++, k++) { + font->name = erealloc(font->name, (n+1) * sizeof(char)); + font->name[n] = words[i][k]; + } + font->name = erealloc(font->name, (n+1) * sizeof(char)); + font->name[n] = ' '; + n++; + } + n--; + font->name[n] = 0; + presence->font.name = true; + } + for (w = 0; words[w]; w++) { + free(words[w]); + } + free(words); + return font; +} + +static struct ChoStyle * +cho_style_parse( + const char *tag_name, + struct Attr **attrs, + struct ChoStyle *inherited_style, + struct ChoStylePresence *presence +) +{ + size_t value_len, last_char; + struct RGBColor *rgb_color; + struct ChoStyle *style; + struct Font *font; + if (inherited_style) { + style = cho_style_copy(inherited_style); + } else { + style = cho_style_new_default(); + } + if (!strcmp(tag_name, "span")) { + int a; + for (a = 0; attrs[a]; a++) { + if (!strcmp(attrs[a]->name, "font_desc")) { + font = cho_style_font_desc_parse(attrs[a]->value, presence); + if (font) { + if (!font->name) { + font->name = strdup(style->font->name); + } + cho_font_free(style->font); + style->font = font; + } + } else if ( + !strcmp(attrs[a]->name, "font_family") || + !strcmp(attrs[a]->name, "face") + ) { + if (!strcmp(attrs[a]->value, "normal")) { + style->font->family = FF_NORMAL; + presence->font.family = true; + } else if (!strcmp(attrs[a]->value, "sans")) { + style->font->family = FF_SANS; + presence->font.family = true; + } else if (!strcmp(attrs[a]->value, "serif")) { + style->font->family = FF_SERIF; + presence->font.family = true; + } else if (!strcmp(attrs[a]->value, "monospace")) { + style->font->family = FF_MONOSPACE; + presence->font.family = true; + } else { + cho_log(LOG_ERR, "Invalid value in attribute 'font_family/face'."); + return NULL; + } + } else if (!strcmp(attrs[a]->name, "size")) { + value_len = strlen(attrs[a]->value); + last_char = value_len - 1; + if (attrs[a]->value[last_char] == '%') { + if (value_len < 5) { + attrs[a]->value[last_char] = 0; + int percentage = atoi(attrs[a]->value); + if (percentage != 0 && percentage <= 100) { + style->font->size *= percentage / 100.0; + presence->font.size = true; + } else { + cho_log(LOG_ERR, "Invalid percentage in attribute 'size'."); + return NULL; + } + } else { + cho_log(LOG_ERR, "Invalid percentage in attribute 'size'."); + return NULL; + } + } else if (isdigit(attrs[a]->value[0]) != 0) { + double size = strtod(attrs[a]->value, NULL); + if (size != 0.0) { + style->font->size = size; + presence->font.size = true; + } else { + cho_log(LOG_ERR, "Invalid number in attribute 'size'."); + return NULL; + } + } else if (!strcmp(attrs[a]->value, "xx-small")) { + style->font->size *= 0.8; + style->font->size *= 0.8; + style->font->size *= 0.8; + presence->font.size = true; + } else if (!strcmp(attrs[a]->value, "x-small")) { + style->font->size *= 0.8; + style->font->size *= 0.8; + presence->font.size = true; + } else if (!strcmp(attrs[a]->value, "small")) { + style->font->size *= 0.8; + presence->font.size = true; + // } else if (!strcmp(attrs[a]->value, "medium")) { + } else if (!strcmp(attrs[a]->value, "large")) { + style->font->size *= 1.8; + presence->font.size = true; + } else if (!strcmp(attrs[a]->value, "x-large")) { + style->font->size *= 1.8; + style->font->size *= 1.8; + presence->font.size = true; + } else if (!strcmp(attrs[a]->value, "xx-large")) { + style->font->size *= 1.8; + style->font->size *= 1.8; + style->font->size *= 1.8; + presence->font.size = true; + } else if (!strcmp(attrs[a]->value, "larger")) { + style->font->size *= 1.8; + presence->font.size = true; + } else if (!strcmp(attrs[a]->value, "smaller")) { + style->font->size *= 0.8; + presence->font.size = true; + } else { + cho_log(LOG_ERR, "Invalid value '%s' for the attribute 'size'.", attrs[a]->value); + return NULL; + } + } else if (!strcmp(attrs[a]->name, "style")) { + if (!strcmp(attrs[a]->value, "normal")) { + style->font->style = FS_ROMAN; + presence->font.style = true; + } else if (!strcmp(attrs[a]->value, "oblique")) { + style->font->style = FS_OBLIQUE; + presence->font.style = true; + } else if (!strcmp(attrs[a]->value, "italic")) { + style->font->style = FS_ITALIC; + presence->font.style = true; + } else { + cho_log(LOG_ERR, "Invalid value in attribute 'style'."); + return NULL; + } + } else if (!strcmp(attrs[a]->name, "weight")) { + if (!strcmp(attrs[a]->value, "normal")) { + style->font->weight = FW_REGULAR; + presence->font.weight = true; + } else if (!strcmp(attrs[a]->value, "bold")) { + style->font->weight = FW_BOLD; + presence->font.weight = true; + } else { + cho_log(LOG_ERR, "Invalid value in attribute 'weight'."); + return NULL; + } + } else if (!strcmp(attrs[a]->name, "foreground")) { + rgb_color = cho_color_parse(attrs[a]->value); + if (!rgb_color) { + LOG_DEBUG("cho_color_parse failed."); + return NULL; + } else { + free(style->foreground_color); + style->foreground_color = rgb_color; + presence->foreground_color = true; + } + } else if (!strcmp(attrs[a]->name, "background")) { + rgb_color = cho_color_parse(attrs[a]->value); + if (!rgb_color) { + LOG_DEBUG("cho_color_parse failed."); + return NULL; + } else { + free(style->background_color); + style->background_color = rgb_color; + presence->background_color = true; + } + } else if (!strcmp(attrs[a]->name, "underline")) { + if (!strcmp(attrs[a]->value, "single")) { + style->underline_style = LS_SINGLE; + presence->underline_style = true; + } else if (!strcmp(attrs[a]->value, "double")) { + style->underline_style = LS_DOUBLE; + presence->underline_style = true; + } else if (!strcmp(attrs[a]->value, "none")) { + style->underline_style = LS_NONE; + presence->underline_style = true; + } else { + cho_log(LOG_ERR, "Invalid value in attribute 'underline'."); + return NULL; + } + } else if (!strcmp(attrs[a]->name, "underline_colour")) { + rgb_color = cho_color_parse(attrs[a]->value); + if (!rgb_color) { + LOG_DEBUG("cho_color_parse failed."); + return NULL; + } else { + free(style->underline_color); + style->underline_color = rgb_color; + presence->underline_color = true; + } + } else if (!strcmp(attrs[a]->name, "overline")) { + if (!strcmp(attrs[a]->value, "single")) { + style->overline_style = LS_SINGLE; + presence->overline_style = true; + } else if (!strcmp(attrs[a]->value, "double")) { + style->overline_style = LS_DOUBLE; + presence->overline_style = true; + } else if (!strcmp(attrs[a]->value, "none")) { + style->overline_style = LS_NONE; + presence->overline_style = true; + } else { + cho_log(LOG_ERR, "Invalid value in attribute 'overline'."); + return NULL; + } + } else if (!strcmp(attrs[a]->name, "overline_colour")) { + rgb_color = cho_color_parse(attrs[a]->value); + if (!rgb_color) { + LOG_DEBUG("cho_color_parse failed."); + return NULL; + } else { + free(style->overline_color); + style->overline_color = rgb_color; + presence->overline_color = true; + } + } else if (!strcmp(attrs[a]->name, "rise")) { + value_len = strlen(attrs[a]->value); + last_char = value_len - 1; + if (attrs[a]->value[0] == '-') { + if (attrs[a]->value[last_char] == '%') { + if (value_len < 6) { + attrs[a]->value[last_char] = 0; + int percentage = atoi(&attrs[a]->value[1]); + if (percentage != 0 && percentage <= 100) { + style->rise = (style->font->size / 2) * percentage / 100.0; + presence->rise = true; + } else { + cho_log(LOG_ERR, "Invalid percentage in attribute 'rise'."); + return NULL; + } + } + } else if (isdigit(attrs[a]->value[1]) != 0) { + double rise = strtod(attrs[a]->value, NULL); + if (rise != 0.0) { + style->rise = rise; + presence->rise = true; + } else { + cho_log(LOG_ERR, "Invalid number in attribute 'rise'."); + return NULL; + } + } else { + cho_log(LOG_ERR, "Invalid value '%s' for the attribute 'rise'.", attrs[a]->value); + return NULL; + } + } else { + if (attrs[a]->value[last_char] == '%') { + if (value_len < 5) { + attrs[a]->value[last_char] = 0; + int percentage = atoi(attrs[a]->value); + if (percentage != 0 && percentage <= 100) { + // TODO: Test if that's right + double more = style->font->size / 2.0 * percentage / 100.0; + style->rise += more; + presence->rise = true; + } else { + cho_log(LOG_ERR, "Invalid percentage in attribute 'rise'."); + return NULL; + } + } + } else if (isdigit(attrs[a]->value[1]) != 0) { + double rise = strtod(attrs[a]->value, NULL); + if (rise != 0.0) { + style->rise = rise; + presence->rise = true; + } else { + cho_log(LOG_ERR, "Invalid number in attribute 'rise'."); + return NULL; + } + } else { + cho_log(LOG_ERR, "Invalid value '%s' for the attribute 'rise'.", attrs[a]->value); + return NULL; + } + } + } else if (!strcmp(attrs[a]->name, "strikethrough")) { + if (!strcmp(attrs[a]->value, "true")) { + style->strikethrough = true; + presence->strikethrough = true; + } else if (!strcmp(attrs[a]->value, "false")) { + style->strikethrough = false; + presence->strikethrough = true; + } else { + cho_log(LOG_ERR, "Invalid value '%s' in attribute 'strikethrough'.", attrs[a]->value); + return NULL; + } + } else if (!strcmp(attrs[a]->name, "strikethrough_colour")) { + rgb_color = cho_color_parse(attrs[a]->value); + if (!rgb_color) { + LOG_DEBUG("cho_color_parse failed."); + return NULL; + } else { + free(style->strikethrough_color); + style->strikethrough_color = rgb_color; + presence->strikethrough_color = true; + } + } else if (!strcmp(attrs[a]->name, "href")) { + style->href = strdup(attrs[a]->value); + presence->href = true; + } else { + cho_log(LOG_ERR, "Invalid attribute '%s'.", attrs[a]->name); + return NULL; + } + } + } else if (!strcmp(tag_name, "b")) { + style->font->weight = FW_BOLD; + presence->font.weight = true; + } else if (!strcmp(tag_name, "big")) { + style->font->size *= 0.8; + presence->font.size = true; + } else if (!strcmp(tag_name, "i")) { + style->font->style = FS_ITALIC; + presence->font.style = true; + } else if (!strcmp(tag_name, "s")) { + style->strikethrough = true; + presence->strikethrough = true; + } else if (!strcmp(tag_name, "sub")) { + style->font->size *= 0.8; + style->rise = -style->font->size * 0.3; + presence->font.size = true; + presence->rise = true; + } else if (!strcmp(tag_name, "sup")) { + style->font->size *= 0.8; + style->rise = style->font->size * 0.3; + presence->font.size = true; + presence->rise = true; + } else if (!strcmp(tag_name, "small")) { + style->font->size *= 0.8; + presence->font.size = true; + } else if (!strcmp(tag_name, "tt")) { + style->font->family = FF_MONOSPACE; + presence->font.family = true; + } else if (!strcmp(tag_name, "u")) { + style->underline_style = LS_SINGLE; + presence->underline_style = true; + } else { + cho_log(LOG_ERR, "Invalid tag name '%s'.", tag_name); + cho_style_free(style); + return NULL; + } + return style; +} + +void +cho_style_print_as_toml(struct ChoStyle *style, const char *section) +{ + printf("foreground_color = \"%s\"\n", cho_rgbcolor_to_string(style->foreground_color)); + printf("background_color = \"%s\"\n", cho_rgbcolor_to_string(style->background_color)); + printf("underline_style = \"%s\"\n", cho_linestyle_to_config_string(style->underline_style)); + printf("underline_color = \"%s\"\n", cho_rgbcolor_to_string(style->underline_color)); + printf("overline_style = \"%s\"\n", cho_linestyle_to_config_string(style->overline_style)); + printf("overline_color = \"%s\"\n", cho_rgbcolor_to_string(style->overline_color)); + printf("strikethrough = %s\n", style->strikethrough ? "true" : "false"); + printf("strikethrough_color = \"%s\"\n", cho_rgbcolor_to_string(style->strikethrough_color)); + printf("boxed = %s\n", style->boxed ? "true" : "false"); + printf("boxed_color = \"%s\"\n", cho_rgbcolor_to_string(style->boxed_color)); + printf("rise = %.1f\n", style->rise); + printf("href = \"%s\"\n\n", style->href ? style->href : ""); + cho_font_print_as_toml(style->font, section); +} + +static bool +cho_style_change_default(struct StyleProperty sprop) +{ + if (sprop.type == -1) + return false; + unsigned int i; + for (i = 0; i<LENGTH(default_style_properties); i++) { + if ( + default_style_properties[i].ttype == sprop.ttype && + default_style_properties[i].type == sprop.type + ) { + switch (sprop.type) { + case SPT_FONT: + free(default_style_properties[i].u.font_name); + if (sprop.u.font_name) { + default_style_properties[i].u.font_name = strdup(sprop.u.font_name); + } else { + default_style_properties[i].u.font_name = NULL; + } + return true; + case SPT_SIZE: + default_style_properties[i].u.font_size = sprop.u.font_size; + return true; + case SPT_COLOR: + free(default_style_properties[i].u.foreground_color); + if (sprop.u.foreground_color) { + default_style_properties[i].u.foreground_color = cho_rgbcolor_copy(sprop.u.foreground_color); + } else { + default_style_properties[i].u.foreground_color = NULL; + } + return true; + default: + cho_log(LOG_ERR, "Invalid style property type '%d'.", sprop.type); + return false; + } + } + } + return false; +} + +static bool +cho_style_reset_default(void) +{ + unsigned int i; + for (i = 0; i<LENGTH(default_style_properties); i++) { + switch (default_style_properties[i].type) { + case SPT_FONT: + free(default_style_properties[i].u.font_name); + default_style_properties[i].u.font_name = NULL; + return true; + case SPT_SIZE: + default_style_properties[i].u.font_size = EMPTY_DOUBLE; + return true; + case SPT_COLOR: + free(default_style_properties[i].u.foreground_color); + default_style_properties[i].u.foreground_color = NULL; + return true; + default: + cho_log(LOG_ERR, "Invalid style property type '%d'.", default_style_properties[i].type); + return false; + } + } + return false; +} + +static struct Attr * +cho_tag_attr_new(void) +{ + struct Attr *attr = emalloc(sizeof(struct Attr)); + attr->name = NULL; + attr->value = NULL; + return attr; +} + +static void +cho_tag_attr_free(struct Attr *attr) +{ + if (!attr) { + return; + } + free(attr->name); + free(attr->value); + free(attr); +} + +static void +cho_tag_attrs_free(struct Attr **attrs) +{ + if (!attrs) { + return; + } + struct Attr **a; + for (a = attrs; *a; a++) { + cho_tag_attr_free(*a); + } + free(attrs); +} + +static struct Tag * +cho_tag_new(void) +{ + struct Tag *tag = emalloc(sizeof(struct Tag)); + tag->name = NULL; + tag->style = NULL; + tag->attrs = NULL; + tag->is_closed = false; + memset(&tag->style_presence, 0, sizeof(tag->style_presence)); + return tag; +} + +static void +cho_tag_free(struct Tag *tag) +{ + if (!tag) { + return; + } + free(tag->name); + if (tag->style) { + cho_style_free(tag->style); + } + if (tag->attrs) { + cho_tag_attrs_free(tag->attrs); + } + free(tag); +} + +static bool +cho_tag_close_last_unclosed(const char *tag_name, struct Tag **tags, int last_index) +{ + int i; + for (i = last_index; i >= 0; i--) { + if (!strcmp(tags[i]->name, tag_name) && !tags[i]->is_closed) { + tags[i]->is_closed = true; + return true; + } + } + cho_log(LOG_ERR, "Didn't find a start tag for the end tag '%s'.", tag_name); + return false; +} + +static struct ChoStyle * +cho_tag_style_inherit(struct Tag **tags, int prev_index) +{ + int i; + for (i = prev_index; i >= 0; i--) { + if (!tags[i]->is_closed) { + return tags[i]->style; + } + } + /* + Doesn't mean there is an error. + If no style can be inherited the + default style should be used. + */ + return NULL; +} + +static struct ChoMetadata * +cho_metadata_new(void) +{ + struct ChoMetadata *meta = emalloc(sizeof(struct ChoMetadata)); + meta->name = NULL; + meta->value = NULL; + meta->style = cho_style_new_default(); + return meta; +} + +static void +cho_metadata_free(struct ChoMetadata *meta) +{ + if (!meta) { + return; + } + free(meta->name); + free(meta->value); + if (meta->style) { + cho_style_free(meta->style); + } + free(meta); +} + +const char * +cho_metadata_get(struct ChoMetadata **metadata, const char *name) +{ + int m; + for (m = 0; metadata[m]; m++) { + if (!strcmp(metadata[m]->name, name)) { + return metadata[m]->value; + } + } + return NULL; +} + +static struct ChoMetadata * +cho_metadata_split(const char *directive_value) +{ + struct ChoMetadata *meta = cho_metadata_new(); + bool is_name = true; + char *value = str_remove_leading_whitespace(directive_value); + int n = 0; + int v = 0; + int i; + for (i = 0; value[i]; i++) { + if (value[i] == ' ') { + meta->name = erealloc(meta->name, (n+1) * sizeof(char)); + meta->name[n] = 0; + is_name = false; + } else { + if (is_name) { + meta->name = erealloc(meta->name, (n+1) * sizeof(char)); + meta->name[n] = value[i]; + n++; + } else { + meta->value = erealloc(meta->value, (v+1) * sizeof(char)); + meta->value[v] = value[i]; + v++; + } + } + } + meta->value = erealloc(meta->value, (v+1) * sizeof(char)); + meta->value[v] = 0; + free(value); + if (v > 1) { + return meta; + } else { + free(meta->name); + free(meta->value); + free(meta); + cho_log(LOG_ERR, "Failed to parse directive 'meta'."); + return NULL; + } +} + +static bool +transposition_parse(const char *str, int *transpose) +{ + long i; + char *endptr; + i = strtol(str, &endptr, 10); + if (str == endptr) { + return false; + } + if (i == LONG_MIN || i == LONG_MAX) { + return false; + } + if (i > INT_MAX) { + return false; + } + *transpose = (int)i; + return true; +} + +static char * +transposition_calc_chord_root(int index, enum NoteType type) +{ + int transpose = *g_transpose; + if (transpose == 0) { + switch (type) { + case NT_NOTE: + return strdup(g_config->output->notes[index]->note); + case NT_SHARP: + return strdup(g_config->output->notes[index]->sharp); + case NT_FLAT: + return strdup(g_config->output->notes[index]->flat); + } + } + int new_index = index; + enum NoteType note_type = type; + int i; + if (transpose > 0) { + for (i = 0; i<transpose; i++) { + switch (note_type) { + case NT_NOTE: + switch (new_index) { + case 2: + new_index++; + break; + case 6: + new_index = 0; + break; + default: + note_type = NT_SHARP; + } + break; + case NT_SHARP: + new_index++; + note_type = NT_NOTE; + break; + case NT_FLAT: + note_type = NT_NOTE; + break; + } + } + } else { + for (i = transpose; i<0; i++) { + switch (note_type) { + case NT_NOTE: + switch (new_index) { + case 0: + new_index = 6; + break; + case 3: + new_index--; + break; + default: + note_type = NT_FLAT; + } + break; + case NT_SHARP: + note_type = NT_NOTE; + break; + case NT_FLAT: + new_index--; + note_type = NT_NOTE; + break; + } + } + } + switch (note_type) { + case NT_NOTE: + return strdup(g_config->output->notes[new_index]->note); + case NT_SHARP: + return strdup(g_config->output->notes[new_index]->sharp); + case NT_FLAT: + return strdup(g_config->output->notes[new_index]->flat); + } + cho_log(LOG_ERR, "Invalid NoteType '%d'.", note_type); + return NULL; +} + +static struct ChoChord * +cho_chord_new(void) +{ + struct ChoChord *chord = emalloc(sizeof(struct ChoChord)); + chord->style = cho_style_new_default(); + chord->name = NULL; + chord->is_canonical = false; + chord->root = NULL; + chord->qual = -1; + chord->ext = NULL; + chord->bass = NULL; + return chord; +} + +/* INFO: copy every field except for 'style' */ +static void +cho_chord_complete(struct ChoChord *first, struct ChoChord *second) +{ + first->name = strdup(second->name); + first->is_canonical = second->is_canonical; + first->qual = second->qual; + if (second->root) { + first->root = strdup(second->root); + } + if (second->ext) { + first->ext = strdup(second->ext); + } + if (second->bass) { + first->bass = strdup(second->bass); + } +} + + +static void +cho_chord_free(struct ChoChord *chord) +{ + if (!chord) { + return; + } + cho_style_free(chord->style); + free(chord->name); + free(chord->root); + free(chord->ext); + free(chord->bass); + free(chord); +} + +static struct ChoChord * +cho_chord_copy(struct ChoChord *chord) +{ + struct ChoChord *copy = emalloc(sizeof(struct ChoChord)); + copy->style = cho_style_copy(chord->style); + copy->is_canonical = chord->is_canonical; + copy->qual = chord->qual; + copy->name = strdup(chord->name); + copy->root = chord->root ? strdup(chord->root) : NULL; + copy->ext = chord->ext ? strdup(chord->ext) : NULL; + copy->bass = chord->bass ? strdup(chord->bass) : NULL; + return copy; +} + +bool +cho_chords_has(struct ChoChord **chords, struct ChoChord *chord) +{ + if (!chords) { + return false; + } + struct ChoChord **c; + for (c = chords; *c; c++) { + if ( + !str_compare((*c)->name, chord->name) && + !str_compare((*c)->root, chord->root) && + !str_compare((*c)->ext, chord->ext) && + !str_compare((*c)->bass, chord->bass) && + (*c)->qual == chord->qual + ) { + return true; + } + } + return false; +} + +int +cho_chord_compare(const void *a, const void *b) +{ + struct ChoChord **aa = (struct ChoChord **)a; + struct ChoChord **bb = (struct ChoChord **)b; + return str_compare((*aa)->root, (*bb)->root); +} + +size_t +cho_chord_count(struct ChoChord **chords) +{ + if (!chords) { + return 0; + } + int i; + for (i = 0; chords[i]; i++); + return i; +} + +void +cho_chords_add(struct ChoChord ***chords, struct ChoChord *chord) +{ + int i = 0; + if (*chords) { + struct ChoChord **c; + for (c = *chords; *c; c++, i++); + } + *chords = erealloc(*chords, (i+2) * sizeof(struct ChoChord *)); + (*chords)[i] = cho_chord_copy(chord); + (*chords)[i+1] = NULL; +} + +void +cho_chords_free(struct ChoChord **chords) +{ + if (!chords) { + return; + } + struct ChoChord **c; + for (c = chords; *c; c++) { + cho_chord_free(*c); + } + free(chords); +} + +/* returns how many bytes make up the root; returns 0 if no root was found */ +static int +cho_chord_root_parse(const char *str, struct ChoChord *chord) +{ + const char *note = NULL; + const char *sharp = NULL; + const char *flat = NULL; + char *transposed_root; + int i; + for (i = 0; i<7; i++) { + sharp = g_config->parser->notes[i]->sharp; + if (sharp && str_starts_with(str, sharp)) { + transposed_root = transposition_calc_chord_root(i, NT_SHARP); + if (!transposed_root) { + LOG_DEBUG("transposition_calc_chord_root failed."); + return 0; + } + chord->root = transposed_root; + return strlen(sharp); + } + flat = g_config->parser->notes[i]->flat; + if (flat && str_starts_with(str, flat)) { + transposed_root = transposition_calc_chord_root(i, NT_FLAT); + if (!transposed_root) { + LOG_DEBUG("transposition_calc_chord_root failed."); + return 0; + } + chord->root = transposed_root; + return strlen(flat); + } + note = g_config->parser->notes[i]->note; + if (str_starts_with(str, note)) { + transposed_root = transposition_calc_chord_root(i, NT_NOTE); + if (!transposed_root) { + LOG_DEBUG("transposition_calc_chord_root failed."); + return 0; + } + chord->root = transposed_root; + return strlen(note); + } + } + return 0; +} + +static char * +cho_chord_qualifier_strip(const char *str) +{ + if (str_starts_with(str, "m") || str_starts_with(str, "-")) { + return strdup((char *)&str[1]); + } + return strdup(str); +} + +static int +cho_chord_qualifier_and_extension_parse(const char *str, struct ChoChord *chord) +{ + int i; + for (i = 0; chord_extensions_major[i]; i++) { + if (str_starts_with(str, chord_extensions_major[i])) { + chord->ext = strdup(chord_extensions_major[i]); + chord->qual = CQ_MAJ; + return strlen(chord_extensions_major[i]); + } + } + for (i = 0; chord_extensions_minor[i]; i++) { + if (str_starts_with(str, chord_extensions_minor[i])) { + chord->ext = cho_chord_qualifier_strip(chord_extensions_minor[i]); + chord->qual = CQ_MIN; + return strlen(chord_extensions_minor[i]); + } + } + if (str_starts_with(str, "aug")) { + chord->qual = CQ_AUG; + return 3; + } + if (str_starts_with(str, "+")) { + chord->qual = CQ_AUG; + return 1; + } + if (str_starts_with(str, "dim")) { + chord->qual = CQ_DIM; + return 3; + } + if (str_starts_with(str, "0")) { + chord->qual = CQ_DIM; + return 1; + } + if (str_starts_with(str, "ø")) { + chord->qual = CQ_DIM; + return 1; + } + // TODO: What about 'ø', 'h', 'h7' and 'h9'? + // TODO: What about extensions after 'aug', '+', 'dim', '0'? + return 0; +} + +static int +cho_chord_bass_parse(const char *str, struct ChoChord *chord) +{ + if (str[0] == '/') { + const char *note = NULL; + const char *sharp = NULL; + const char *flat = NULL; + const char *out_note = NULL; + const char *out_sharp = NULL; + const char *out_flat = NULL; + int i; + for (i = 0; i<7; i++) { + sharp = g_config->parser->notes[i]->sharp; + out_sharp = g_config->output->notes[i]->sharp; + if (sharp && !strcmp((char *)&str[1], sharp)) { + chord->bass = strdup(out_sharp); + return strlen(sharp) + 1; + } + flat = g_config->parser->notes[i]->flat; + out_flat = g_config->output->notes[i]->flat; + if (flat && !strcmp((char *)&str[1], flat)) { + chord->bass = strdup(out_flat); + return strlen(flat) + 1; + } + note = g_config->parser->notes[i]->note; + out_note = g_config->parser->notes[i]->note; + if (!strcmp((char *)&str[1], note)) { + chord->bass = strdup(out_note); + return strlen(note) + 1; + } + } + } + return 0; +} + +static struct ChoChord * +cho_chord_parse(const char *str) +{ + struct ChoChord *chord = cho_chord_new(); + size_t str_len = strlen(str); + size_t bytes_parsed = 0; + int ret; + chord->name = strdup(str); + ret = cho_chord_root_parse(str, chord); + if (ret == 0) { + return chord; + } + chord->qual = CQ_MAJ; + bytes_parsed += ret; + if (bytes_parsed == str_len) { + chord->is_canonical = true; + return chord; + } + ret = cho_chord_qualifier_and_extension_parse((const char *)&str[bytes_parsed], chord); + bytes_parsed += ret; + if (bytes_parsed == str_len) { + chord->is_canonical = true; + return chord; + } + ret = cho_chord_bass_parse((const char *)&str[bytes_parsed], chord); + bytes_parsed += ret; + if (bytes_parsed == str_len) { + chord->is_canonical = true; + return chord; + } + return chord; +} + +char * +cho_chord_name_generate(struct ChoChord *chord) +{ + if (chord->is_canonical) { + int n = 0; + int i; + char *name = NULL; + size_t name_len = 0; + name_len += strlen(chord->root); + name = erealloc(name, name_len * sizeof(char)); + for (i = 0; chord->root[i]; i++) { + name[n] = chord->root[i]; + n++; + } + const char *qual = chord_qualifiers[chord->qual]; + name_len += strlen(qual); + name = erealloc(name, name_len * sizeof(char)); + for (i = 0; qual[i]; i++) { + name[n] = qual[i]; + n++; + } + if (chord->ext) { + name_len += strlen(chord->ext); + name = erealloc(name, name_len * sizeof(char)); + for (i = 0; chord->ext[i]; i++) { + name[n] = chord->ext[i]; + n++; + } + } + if (chord->bass) { + name_len++; + name_len += strlen(chord->bass); + name = erealloc(name, name_len * sizeof(char)); + name[n] = '/'; + n++; + for (i = 0; chord->bass[i]; i++) { + name[n] = chord->bass[i]; + n++; + } + } + name_len++; + name = erealloc(name, name_len * sizeof(char)); + name[n] = 0; + return name; + } + return strdup(chord->name); +} + +static struct ChoImage * +cho_image_new(void) +{ + struct ChoImage *image = emalloc(sizeof(struct ChoImage)); + image->is_asset = false; + image->id = NULL; + image->src = NULL; + image->width = NULL; + image->height = NULL; + image->width_scale = NULL; + image->height_scale = NULL; + image->align = A_CENTER; + image->border = EMPTY_DOUBLE; + image->spread_space = NULL; + image->href = NULL; + image->x = NULL; + image->y = NULL; + image->anchor = AN_FLOAT; + image->dx = NULL; + image->dy = NULL; + image->w = NULL; + image->h = NULL; + image->bbox = false; + return image; +} + +static void +cho_image_free(struct ChoImage *image) +{ + if (!image) { + return; + } + free(image->id); + free(image->src); + free(image->width); + free(image->height); + free(image->width_scale); + free(image->height_scale); + free(image->spread_space); + free(image->href); + free(image->x); + free(image->y); + free(image->dx); + free(image->dy); + free(image->w); + free(image->h); + free(image); +} + +static struct ChoImage * +cho_image_copy(struct ChoImage *image) +{ + struct ChoImage *copy = emalloc(sizeof(struct ChoImage)); + copy->is_asset = image->is_asset; + copy->id = strdup(image->id); + copy->src = strdup(image->src); + copy->width = size_copy(image->width); + copy->height = size_copy(image->height); + copy->width_scale = size_copy(image->width_scale); + copy->height_scale = size_copy(image->height_scale); + copy->align = image->align; + copy->border = image->border; + copy->spread_space = size_copy(image->spread_space); + copy->href = strdup(image->href); + copy->x = size_copy(image->x); + copy->y = size_copy(image->y); + copy->anchor = image->anchor; + copy->dx = size_copy(image->dx); + copy->dy = size_copy(image->dy); + copy->w = size_copy(image->w); + copy->h = size_copy(image->h); + copy->bbox = image->bbox; + return copy; +} + +static struct ChoImage * +cho_image_find_asset(const char *id) +{ + int i; + for (i = 0; i<g_ia; i++) { + if (!strcmp(g_image_assets[i]->id, id)) { + return g_image_assets[i]; + } + } + return NULL; +} + +#ifdef DEBUG +void +cho_debug_image_print(struct ChoImage *image) +{ + printf("---- BEGIN IMAGE ----\n"); + printf("is_asset: %d\n", image->is_asset); + if (image->id) { + printf("id: %s\n", image->id); + } else { + printf("id: NULL\n"); + } + if (image->src) { + printf("src: %s\n", image->src); + } else { + printf("src: NULL\n"); + } + if (image->width) { + printf("width: %s\n", size_to_string(image->width)); + } else { + printf("width: NULL\n"); + } + if (image->height) { + printf("height: %s\n", size_to_string(image->height)); + } else { + printf("height: NULL\n"); + } + if (image->width_scale) { + printf("width_scale: %s\n", size_to_string(image->width_scale)); + } else { + printf("width_scale: NULL\n"); + } + if (image->height_scale) { + printf("height_scale: %s\n", size_to_string(image->height_scale)); + } else { + printf("height_scale: NULL\n"); + } + printf("align: %s\n", alignment_enums[image->align]); + printf("border: %.1f\n", image->border); + if (image->spread_space) { + printf("spread_space: %s\n", size_to_string(image->spread_space)); + } else { + printf("spread_space: NULL\n"); + } + if (image->href) { + printf("href: %s\n", image->href); + } else { + printf("href: NULL\n"); + } + if (image->x) { + printf("x: %s\n", size_to_string(image->x)); + } else { + printf("x: NULL\n"); + } + if (image->y) { + printf("y: %s\n", size_to_string(image->y)); + } else { + printf("y: NULL\n"); + } + printf("anchor: %s\n", anchor_enums[image->anchor]); + if (image->dx) { + printf("dx: %s\n", size_to_string(image->dx)); + } else { + printf("dx: NULL\n"); + } + if (image->dy) { + printf("dy: %s\n", size_to_string(image->dy)); + } else { + printf("dy: NULL\n"); + } + if (image->w) { + printf("w: %s\n", size_to_string(image->w)); + } else { + printf("w: NULL\n"); + } + if (image->h) { + printf("h: %s\n", size_to_string(image->h)); + } else { + printf("h: NULL\n"); + } + printf("bbox: %d\n", image->bbox); + printf("---- END IMAGE ------\n"); +} +#endif /* DEBUG */ + +static bool +cho_image_option_parse(struct ChoImage *image, const char *name, const char *value) +{ + char *endptr; + struct Size *size; + if (!strcmp(name, "id")) { + image->id = strdup(value); + } else + if (!strcmp(name, "src")) { + image->src = filepath_resolve_tilde(value); + if (!image->src) { + LOG_DEBUG("filepath_resolve_tilde failed."); + return false; + } + } else + if (!strcmp(name, "width")) { + size = size_create(value); + if (!size) { + cho_log(LOG_ERR, "Invalid value in option 'width' in image directive."); + return false; + } + if (size->type == ST_EM || size->type == ST_EX) { + cho_log(LOG_ERR, "Invalid type of value in option 'width' in image directive. Allowed types are: points, percentage."); + return false; + } + image->width = size; + } else + if (!strcmp(name, "height")) { + size = size_create(value); + if (!size) { + cho_log(LOG_ERR, "Invalid value in option 'height' in image directive."); + return false; + } + if (size->type == ST_EM || size->type == ST_EX) { + cho_log(LOG_ERR, "Invalid type of value in option 'height' in image directive. Allowed types are: point, percentage."); + return false; + } + image->height = size; + } else + if (!strcmp(name, "scale")) { + char *comma; + if ((comma = strchr(value, ','))) { + *comma = 0; + size = size_create(value); + if (!size) { + cho_log(LOG_ERR, "Invalid value in option 'scale' in image directive."); + return false; + } + if (size->type == ST_EM || size->type == ST_EX) { + cho_log(LOG_ERR, "Invalid type of value in option 'scale' in image directive. Allowed types are: point, percentage."); + return false; + } + image->width_scale = size; + size = size_create(++comma); + if (!size) { + cho_log(LOG_ERR, "Invalid value in option 'scale' in image directive."); + return false; + } + if (size->type == ST_EM || size->type == ST_EX) { + cho_log(LOG_ERR, "Invalid type of value in option 'scale' in image directive. Allowed types are: point, percentage."); + return false; + } + image->height_scale = size; + } else { + size = size_create(value); + if (!size) { + cho_log(LOG_ERR, "Invalid value in option 'scale' in image directive."); + return false; + } + if (size->type == ST_EM || size->type == ST_EX) { + cho_log(LOG_ERR, "Invalid type of value in option 'scale' in image directive. Allowed types are: point, percentage."); + return false; + } + image->width_scale = size; + image->height_scale = size_copy(size); + } + } else + if (!strcmp(name, "align")) { + if (!strcmp(value, "left")) { + image->align = A_LEFT; + } else + if (!strcmp(value, "right")) { + image->align = A_RIGHT; + } else + if (!strcmp(value, "center")) { + image->align = A_CENTER; + } else { + cho_log(LOG_ERR, "Invalid value in option 'align' in image directive."); + return false; + } + } else + if (!strcmp(name, "border")) { + image->border = strtod(value, &endptr); + if (value == endptr || errno == ERANGE) { + LOG_DEBUG("strtod failed."); + return false; + } + } else + if (!strcmp(name, "spread")) { + size = size_create(value); + if (!size) { + cho_log(LOG_ERR, "Invalid value in option 'spread' in image directive."); + return false; + } + if (size->type == ST_EM || size->type == ST_EX) { + cho_log(LOG_ERR, "Invalid type of value in option 'spread' in image directive. Allowed types are: point, percentage."); + return false; + } + image->spread_space = size; + } else + if (!strcmp(name, "href")) { + image->href = strdup(value); + } else + if (!strcmp(name, "x")) { + size = size_create(value); + if (!size) { + cho_log(LOG_ERR, "Invalid value in option 'x' in image directive."); + return false; + } + if (size->type == ST_EM || size->type == ST_EX) { + cho_log(LOG_ERR, "Invalid type of value in option 'x' in image directive. Allowed types are: point, percentage."); + return false; + } + image->x = size; + } else + if (!strcmp(name, "y")) { + size = size_create(value); + if (!size) { + cho_log(LOG_ERR, "Invalid value in option 'y' in image directive."); + return false; + } + if (size->type == ST_EM || size->type == ST_EX) { + cho_log(LOG_ERR, "Invalid type of value in option 'y' in image directive. Allowed types are: point, percentage."); + return false; + } + image->y = size; + } else + if (!strcmp(name, "anchor")) { + if (!strcmp(value, "paper")) { + image->anchor = AN_PAPER; + } else + if (!strcmp(value, "page")) { + image->anchor = AN_PAGE; + } else + if (!strcmp(value, "column")) { + image->anchor = AN_COLUMN; + } else + if (!strcmp(value, "line")) { + image->anchor = AN_LINE; + } else + if (!strcmp(value, "float")) { + image->anchor = AN_FLOAT; + } else { + cho_log(LOG_ERR, "Invalid value in option 'anchor' in image directive."); + return false; + } + } + return true; +} + +static struct ChoImage * +cho_image_directive_parse(const char *str) +{ + struct ChoImage *image = cho_image_new(); + struct ChoImage *asset; + char c; + enum OptionState state = OS_NAME; + enum AttrValueSyntax avs = -1; + char name[6+1]; + char value[URL_MAX_LEN+1]; + int n = 0; + int v = 0; + memset(name, 0, sizeof(name)); + memset(value, 0, sizeof(value)); + int option_count = 0; + int i; + for (i = 0; str[i]; i++) { + c = str[i]; + switch (state) { + case OS_NAME: + if (is_whitespace(c)) { + if (n == 0) { + break; + } else { + name[n] = 0; + cho_log(LOG_ERR, "Option with name '%s' in image directive has no value.", name); + return NULL; + } + } + if (c == '=') { + name[n] = 0; + state = OS_VALUE; + break; + } + if (n > 5) { + cho_log(LOG_ERR, "Option name in image directive is too long."); + return NULL; + } + name[n] = c; + n++; + break; + case OS_VALUE: + if (avs == -1) { + if (is_whitespace(c)) { + cho_log(LOG_ERR, "Whitespace character after equals sign in image directive is invalid."); + return NULL; + } + if (c == '\'') { + avs = AVS_APOSTROPHE; + } else if (c == '"') { + avs = AVS_QUOTATION_MARK; + } else { + avs = AVS_UNQUOTED; + value[v] = c; + v++; + } + break; + } + if (c == '\n') { + cho_log(LOG_ERR, "Newline character inside an option value in image directive is invalid."); + return NULL; + } + if ( + (avs == AVS_APOSTROPHE && c == '\'') || + (avs == AVS_QUOTATION_MARK && c == '"') || + (avs == AVS_UNQUOTED && (c == ' ' || c == '\t')) + ) { + value[v] = 0; + if (!cho_image_option_parse(image, name, value)) { + LOG_DEBUG("cho_image_option_check failed."); + return NULL; + } + option_count++; + memset(name, 0, n); + memset(value, 0, v); + n = 0; + v = 0; + avs = -1; + state = OS_NAME; + break; + } + value[v] = c; + v++; + break; + } + } + if (avs == AVS_UNQUOTED) { + value[v] = 0; + if (!cho_image_option_parse(image, name, value)) { + LOG_DEBUG("cho_image_option_check failed."); + return NULL; + } + option_count++; + } + if (image->id) { + if (image->src) { + if (option_count > 2) { + cho_log(LOG_ERR, "Defining an image asset disallows any options other than 'id' and 'src'."); + return NULL; + } + image->is_asset = true; + } else { + asset = cho_image_find_asset(image->id); + if (!asset) { + cho_log(LOG_ERR, "There is no image asset with the id '%s'.", image->id); + return NULL; + } + image->src = strdup(asset->src); + } + } + return image; +} + +static struct ChoImage * +cho_image_tag_parse(struct Attr **attrs) +{ + struct ChoImage *image = cho_image_new(); + struct ChoImage *asset; + struct Size *size; + int a; + for (a = 0; attrs[a]; a++) { + if (!strcmp(attrs[a]->name, "src")) { + image->src = filepath_resolve_tilde(attrs[a]->value); + if (!image->src) { + LOG_DEBUG("filepath_resolve_tilde failed."); + return NULL; + } + } else + if (!strcmp(attrs[a]->name, "id")) { + image->id = strdup(attrs[a]->value); + } else + if (!strcmp(attrs[a]->name, "width")) { + size = size_create(attrs[a]->value); + if (!size) { + cho_log(LOG_ERR, "Invalid value in attribute 'width' in 'img' tag."); + return NULL; + } + if (size->type == ST_PERCENT) { + cho_log(LOG_ERR, "Invalid type of value in attribute 'width' in 'img' tag. Allowed types are: point, em, ex."); + return NULL; + } + image->width = size; + } else + if (!strcmp(attrs[a]->name, "height")) { + size = size_create(attrs[a]->value); + if (!size) { + cho_log(LOG_ERR, "Invalid value in attribute 'height' in 'img' tag."); + return NULL; + } + if (size->type == ST_PERCENT) { + cho_log(LOG_ERR, "Invalid type of value in attribute 'height' in 'img' tag. Allowed types are: point, em, ex."); + return NULL; + } + image->height = size; + } else + if (!strcmp(attrs[a]->name, "dx")) { + size = size_create(attrs[a]->value); + if (!size) { + cho_log(LOG_ERR, "Invalid value in attribute 'dx' in 'img' tag."); + return NULL; + } + if (size->type == ST_PERCENT) { + cho_log(LOG_ERR, "Invalid type of value in attribute 'dx' in 'img' tag. Allowed types are: point, em, ex."); + return NULL; + } + image->dx = size; + } else + if (!strcmp(attrs[a]->name, "dy")) { + size = size_create(attrs[a]->value); + if (!size) { + cho_log(LOG_ERR, "Invalid value in attribute 'dy' in 'img' tag."); + return NULL; + } + if (size->type == ST_PERCENT) { + cho_log(LOG_ERR, "Invalid type of value in attribute 'dy' in 'img' tag. Allowed types are: point, em, ex."); + return NULL; + } + image->dy = size; + } else + if (!strcmp(attrs[a]->name, "scale")) { + char *comma; + if ((comma = strchr(attrs[a]->value, ','))) { + *comma = 0; + size = size_create(attrs[a]->value); + if (!size) { + cho_log(LOG_ERR, "Invalid value in attribute 'scale' in 'img' tag."); + return NULL; + } + if (size->type == ST_EM || size->type == ST_EX) { + cho_log(LOG_ERR, "Invalid type of value in attribute 'scale' in 'img' tag. Allowed types are: point, percent"); + return NULL; + } + image->width_scale = size; + size = size_create(++comma); + if (!size) { + cho_log(LOG_ERR, "Invalid value in attribute 'scale' in 'img' tag."); + return NULL; + } + if (size->type == ST_EM || size->type == ST_EX) { + cho_log(LOG_ERR, "Invalid type of value in attribute 'scale' in 'img' tag. Allowed types are: point, percent"); + return NULL; + } + image->height_scale = size; + } else { + size = size_create(attrs[a]->value); + if (!size) { + cho_log(LOG_ERR, "Invalid value in attribute 'scale' in 'img' tag."); + return NULL; + } + if (size->type == ST_EM || size->type == ST_EX) { + cho_log(LOG_ERR, "Invalid type of value in attribute 'scale' in 'img' tag. Allowed types are: point, percent"); + return NULL; + } + image->width_scale = size; + image->height_scale = size_copy(size); + } + } else + if (!strcmp(attrs[a]->name, "align")) { + if (!strcmp(attrs[a]->value, "left")) { + image->align = A_LEFT; + } else + if (!strcmp(attrs[a]->value, "right")) { + image->align = A_RIGHT; + } else + if (!strcmp(attrs[a]->value, "center")) { + image->align = A_CENTER; + } else { + cho_log(LOG_ERR, "Invalid value in attribute 'align' in 'img' tag."); + return NULL; + } + } else + if (!strcmp(attrs[a]->name, "bbox")) { + if (!strcmp(attrs[a]->value, "1")) { + image->bbox = true; + } else + if (!strcmp(attrs[a]->value, "0")) { + image->bbox = false; + } else { + cho_log(LOG_ERR, "Invalid value in attribute 'bbox' in 'img' tag."); + return NULL; + } + } else + if (!strcmp(attrs[a]->name, "w")) { + size = size_create(attrs[a]->value); + if (!size) { + cho_log(LOG_ERR, "Invalid value in attribute 'w' in 'img' tag."); + return NULL; + } + if (size->type != ST_POINT) { + cho_log(LOG_ERR, "Invalid type of value in attribute 'w' in 'img' tag. Allowed type is: point"); + return NULL; + } + image->w = size; + } else + if (!strcmp(attrs[a]->name, "h")) { + size = size_create(attrs[a]->value); + if (!size) { + cho_log(LOG_ERR, "Invalid value in attribute 'h' in 'img' tag."); + return NULL; + } + if (size->type != ST_POINT) { + cho_log(LOG_ERR, "Invalid type of value in attribute 'h' in 'img' tag. Allowed type is: point"); + return NULL; + } + image->h = size; + } else { + cho_log(LOG_ERR, "Invalid attribute '%s' in 'img' tag.", attrs[a]->name); + return NULL; + } + } + if (image->id) { + if (image->src) { + cho_log(LOG_ERR, "'img' tag can't have both attributes 'id' and 'src' at the same time."); + return NULL; + } else { + asset = cho_image_find_asset(image->id); + if (!asset) { + cho_log(LOG_ERR, "There is no image asset with the id '%s'.", image->id); + return NULL; + } + image->src = strdup(asset->src); + } + } else { + if (!image->src) { + cho_log(LOG_ERR, "'img' tag has to have at least either the attribute 'id' or 'src'."); + return NULL; + } + } + return image; +} + +static int8_t +char_to_positive_int(char c) +{ + if (c >= '0' && c <= '9') { + return c - 48; + } + return -1; +} + +static int8_t +finger_to_int(char c) +{ + if (c == '-' || c == 'x' || c == 'X' || c == 'N') { + return -1; + } + if (c >= '0' && c <= '9') { + return c - 48; + } + if (c >= 'A' && c <= 'Z') { + return c - 64; + } + return -2; +} + +static struct ChordDiagram * +cho_chord_diagram_parse( + const char *str, + struct ChordDiagram **custom_diagrams, + int custom_diagrams_len +) +{ + struct ChordDiagram *diagram = chord_diagram_new(); + enum ChordDiagramState state = CDS_NAME; + enum ChordDiagramContent current_content = -1; + enum ChordDiagramContent future_content = -1; + bool is_maybe_minus_one = false; + char name[20]; + char option[10]; + char key[3]; + char base_fret[3]; + char diagram_value[7+1]; + char chord_to_copy[20]; + int i = 0; + int f = 0; + int fret_count = 0; + int finger_count = 0; + int8_t number = -2; + long l; + const char *c; + for (c = str; *c; c++){ + // printf("c '%c' state '%d'\n", c, state); + switch (state) { + case CDS_NAME: { + if (is_whitespace(*c)) { + if (i == 0) { + break; + } + name[i] = 0; + i = 0; + state = CDS_OPTION_NAME; + break; + } + if (i > 18) { + cho_log(LOG_ERR, "Chord name in chord diagram is too long."); + return NULL; + } + name[i] = *c; + i++; + break; + } + case CDS_OPTION_NAME: { + if (is_whitespace(*c)) { + if (i == 0) { + break; + } + option[i] = 0; + future_content = -1; + if (!strcmp(option, "base-fret")) { + state = CDS_BASE_FRET; + future_content = CDC_STRING; + } else + if (!strcmp(option, "frets")) { + state = CDS_FRETS; + future_content = CDC_STRING; + } else + if (!strcmp(option, "fingers")) { + state = CDS_FINGERS; + future_content = CDC_STRING; + } else + if (!strcmp(option, "keys")) { + state = CDS_KEYS; + future_content = CDC_KEYBOARD; + } else + if (!strcmp(option, "diagram")) { + state = CDS_DIAGRAM; + } else + if (!strcmp(option, "copy")) { + state = CDS_COPY; + } else { + cho_log(LOG_ERR, "Invalid option '%s' in define directive.", option); + return NULL; + } + memset(option, 0, i); + i = 0; + if (current_content == -1 && future_content != -1) { + current_content = future_content; + switch (future_content) { + case CDC_STRING: + diagram->is_string_instrument = true; + diagram->u.sd = string_diagram_new(); + diagram->u.sd->name = strdup(name); + break; + case CDC_KEYBOARD: + diagram->is_string_instrument = false; + diagram->u.kd = keyboard_diagram_new(); + diagram->u.kd->name = strdup(name); + break; + default: + cho_log(LOG_ERR, "'future_content' cannot be empty at this point.\n"); + } + } + break; + } + if (i > 8) { + cho_log(LOG_ERR, "Option in chord diagram is too long."); + return NULL; + } + option[i] = *c; + i++; + break; + } + case CDS_BASE_FRET: { + if (is_whitespace(*c)) { + if (i == 0) { + break; + } + base_fret[i] = 0; + i = 0; + l = str_to_number(base_fret); + if (l == -1) { + LOG_DEBUG("str_to_number failed."); + cho_log(LOG_ERR, "Invalid base-fret value '%s' in chord diagram.", base_fret); + return NULL; + } + if (l == 0) { + cho_log(LOG_ERR, "Invalid base-fret value '%c' in chord diagram.", *c); + return NULL; + } + diagram->u.sd->base_fret = (int8_t)l; + state = CDS_OPTION_NAME; + break; + } + if (i > 1) { + cho_log(LOG_ERR, "base-fret value is too long."); + printf("c: %c\n", *c); + return NULL; + } + base_fret[i] = *c; + i++; + break; + } + case CDS_FRETS: { + number = -2; + if (is_whitespace(*c)) { + break; + } + if (*c == '-') { + is_maybe_minus_one = true; + break; + } + if (is_maybe_minus_one) { + if (*c == '1') { + number = -1; + is_maybe_minus_one = false; + } else { + cho_log(LOG_ERR, "Invalid frets value '-%c' in chord diagram.", *c); + return NULL; + } + } + if (*c == 'N' || *c == 'x') { + number = -1; + } else + if (isalpha(*c)) { + f = 0; + state = CDS_OPTION_NAME; + c--; + break; + } + if (number == -2) { + number = char_to_positive_int(*c); + if (number == -1) { + LOG_DEBUG("char_to_positive_int failed."); + cho_log(LOG_ERR, "Invalid frets value '%c' in chord diagram.", *c); + return NULL; + } + } + if (f > 11) { + cho_log(LOG_ERR, "Too many fret values in chord diagram."); + return NULL; + } + diagram->u.sd->frets[f] = number; + f++; + fret_count++; + break; + } + case CDS_FINGERS: { + if (is_whitespace(*c)) { + break; + } + if (*c == 'b' || *c == 'f') { + f = 0; + state = CDS_OPTION_NAME; + c--; + break; + } + number = finger_to_int(*c); + if (number == -2) { + cho_log(LOG_ERR, "Invalid fingers value '%c' in chord diagram.", *c); + return NULL; + } + if (f > 11) { + cho_log(LOG_ERR, "Too many finger values in chord diagram."); + return NULL; + } + diagram->u.sd->fingers[f] = number; + f++; + finger_count++; + break; + } + case CDS_KEYS: { + if (is_whitespace(*c)) { + if (i == 0) { + break; + } + key[i] = 0; + i = 0; + l = str_to_number(key); + if (l == -1) { + LOG_DEBUG("str_to_number failed."); + cho_log(LOG_ERR, "Invalid number in keys in chord diagram."); + return NULL; + } + if (f > 23) { + cho_log(LOG_ERR, "Too many key values in chord diagram."); + return NULL; + } + diagram->u.kd->keys[f] = (int8_t)l; + f++; + break; + } + if (isalpha(*c)) { + state = CDS_OPTION_NAME; + c--; + break; + } + if (i > 1) { + cho_log(LOG_ERR, "Too high key value in chord diagram. '%d'", i); + printf("key '%s'\n", key); + return NULL; + } + key[i] = *c; + i++; + break; + } + case CDS_DIAGRAM: { + if (is_whitespace(*c)) { + if (i == 0) { + break; + } + diagram_value[i] = 0; + i = 0; + if (!strcmp(diagram_value, "off")) { + diagram->show = false; + } else + if (!strcmp(diagram_value, "on")) { + // INFO: but this is already the default + diagram->show = true; + } else { + diagram->color = cho_color_parse(diagram_value); + if (!diagram->color) { + LOG_DEBUG("cho_color_parse failed."); + return NULL; + } + } + state = CDS_OPTION_NAME; + break; + } + diagram_value[i] = *c; + i++; + break; + } + case CDS_COPY: { + if (is_whitespace(*c)) { + if (i == 0) { + break; + } + chord_to_copy[i] = 0; + if (current_content != -1) { + cho_log(LOG_ERR, "The define options 'base-fret', 'frets', 'fingers' and 'keys' are not allowed before the 'copy' option."); + return NULL; + } + enum Instrument ins = g_config->output->diagram->instrument; + current_content = chord_diagram_duplicate(diagram, custom_diagrams, custom_diagrams_len, name, chord_to_copy, ins); + if (current_content == -1) { + cho_log(LOG_ERR, "Can't copy the diagram for the chord '%s'" + "because no previous definition was found and also" + "no predefined chord diagram for the instrument '%s'" + "was found.", chord_to_copy, instruments[ins]); + return NULL; + } + i = 0; + state = CDS_OPTION_NAME; + break; + } + chord_to_copy[i] = *c; + i++; + break; + } + } + } + switch (state) { + case CDS_BASE_FRET: { + base_fret[i] = 0; + i = 0; + l = str_to_number(base_fret); + if (l == -1) { + LOG_DEBUG("str_to_number failed."); + cho_log(LOG_ERR, "Invalid base-fret value '%s' in chord diagram.", base_fret); + return NULL; + } + if (l == 0) { + cho_log(LOG_ERR, "Invalid base-fret value '%c' in chord diagram.", *c); + return NULL; + } + diagram->u.sd->base_fret = (int8_t)l; + break; + } + case CDS_KEYS: { + key[i] = 0; + if (strlen(key) > 0) { + l = str_to_number(key); + if (l == -1) { + LOG_DEBUG("str_to_number failed."); + cho_log(LOG_ERR, "Invalid number in keys in chord diagram."); + return NULL; + } + if (f > 23) { + cho_log(LOG_ERR, "Too many key values in chord diagram."); + return NULL; + } + diagram->u.kd->keys[f] = (int8_t)l; + } + break; + } + case CDS_DIAGRAM: { + diagram_value[i] = 0; + if (!strcmp(diagram_value, "off")) { + diagram->show = false; + } else + if (!strcmp(diagram_value, "on")) { + // INFO: but this is already the default + diagram->show = true; + } else { + free(diagram->color); + diagram->color = cho_color_parse(diagram_value); + if (!diagram->color) { + LOG_DEBUG("cho_color_parse failed."); + return NULL; + } + } + break; + } + case CDS_COPY: { + chord_to_copy[i] = 0; + if (current_content != -1) { + cho_log(LOG_ERR, "The define options 'base-fret', 'frets'," + "'fingers' and 'keys' are not allowed before the 'copy'" + "option."); + return NULL; + } + enum Instrument ins = g_config->output->diagram->instrument; + current_content = chord_diagram_duplicate(diagram, custom_diagrams, custom_diagrams_len, name, chord_to_copy, ins); + if (current_content == -1) { + cho_log(LOG_ERR, "Can't copy the diagram for the chord '%s' because" + "no previous definition was found and also no predefined" + "chord diagram for the instrument '%s' was found.", + chord_to_copy, instruments[ins]); + return NULL; + } + break; + } + default: + } + if ( + current_content == CDC_STRING && + fret_count > 0 && + finger_count > 0 && + fret_count != finger_count + ) { + cho_log(LOG_ERR, "The number of frets (%d) and fingers (%d) in the chord diagram must be equal.", fret_count, finger_count); + return NULL; + } + return diagram; +} + +static struct ChoText * +cho_text_new(void) +{ + struct ChoText *text = emalloc(sizeof(struct ChoText)); + text->style = cho_style_new_default(); + text->text = NULL; + return text; +} + +static void +cho_text_free(struct ChoText *text) +{ + if (!text) { + return; + } + cho_style_free(text->style); + free(text->text); + free(text); +} + +static struct ChoText * +cho_text_copy(struct ChoText *text) +{ + struct ChoText *copy = emalloc(sizeof(struct ChoText)); + copy->style = cho_style_copy(text->style); + copy->text = strdup(text->text); + return copy; +} + +static void +cho_text_above_free(struct ChoLineItemAbove *text_above) +{ + if (!text_above) { + return; + } + if (text_above->is_chord) { + cho_chord_free(text_above->u.chord); + } else { + cho_text_free(text_above->u.annot); + } + free(text_above); +} + +static struct ChoLineItemAbove * +cho_text_above_copy(struct ChoLineItemAbove *text_above) +{ + struct ChoLineItemAbove *copy = emalloc(sizeof(struct ChoLineItemAbove)); + copy->position = text_above->position; + copy->is_chord = text_above->is_chord; + if (copy->is_chord) { + copy->u.chord = cho_chord_copy(text_above->u.chord); + } else { + copy->u.annot = cho_text_copy(text_above->u.annot); + } + return copy; +} + +static struct ChoLineItem * +cho_line_item_new(void) +{ + struct ChoLineItem *item = emalloc(sizeof(struct ChoLineItem)); + item->is_text = true; + item->u.text = cho_text_new(); + return item; +} + +static void +cho_line_item_free(struct ChoLineItem *item) +{ + if (!item) { + return; + } + if (item->is_text) { + cho_text_free(item->u.text); + } else { + if (item->u.image) { + cho_image_free(item->u.image); + } + } + free(item); +} + +static struct ChoLineItem * +cho_line_item_copy(struct ChoLineItem *item) +{ + struct ChoLineItem *copy = emalloc(sizeof(struct ChoLineItem)); + if (item->is_text) { + copy->is_text = true; + copy->u.text = cho_text_copy(item->u.text); + } else { + copy->is_text = false; + copy->u.image = cho_image_copy(item->u.image); + } + return copy; +} + +static struct ChoLine * +cho_line_new(void) +{ + struct ChoLine *line = emalloc(sizeof(struct ChoLine)); + line->text_above = NULL; + line->items = NULL; + line->btype = BT_LINE; + return line; +} + +static void +cho_line_free(struct ChoLine *line) +{ + if (!line) { + return; + } + struct ChoLineItem **it; + for (it = line->items; *it; it++) { + cho_line_item_free(*it); + } + struct ChoLineItemAbove **above; + for (above = line->text_above; *above; above++) { + cho_text_above_free(*above); + } + free(line->items); + free(line->text_above); + free(line); +} + +static int +cho_line_compute_text_above_position(struct ChoLine *line, int ly, int te) +{ + if (ly == 0) { + return te; + } + size_t lyrics_len = 0; + int i; + for (i = ly-1; i >= 0; i--) { + if (line->items[i]->is_text) { + lyrics_len += strlen(line->items[i]->u.text->text); + } + } + return lyrics_len + te; +} + +static struct ChoSection * +cho_section_new(void) +{ + struct ChoSection *section = emalloc(sizeof(struct ChoSection)); + section->type = -1; + section->label = NULL; + section->lines = NULL; + return section; +} + +static void +cho_section_free(struct ChoSection *section) +{ + if (!section) { + return; + } + if (section->label) { + cho_text_free(section->label); + } + struct ChoLine **li; + for (li = section->lines; *li; li++) { + cho_line_free(*li); + } + free(section->lines); + free(section); +} + +static struct ChoSection * +cho_section_copy(struct ChoSection *section) +{ + struct ChoSection *copy = emalloc(sizeof(struct ChoSection)); + copy->type = section->type; + if (section->label) { + copy->label = cho_text_copy(section->label); + } else { + copy->label = NULL; + } + copy->lines = NULL; + int li, c, ly; + for (li = 0; section->lines[li]; li++) { + copy->lines = erealloc(copy->lines, (li+1) * sizeof(struct ChoLine *)); + copy->lines[li] = cho_line_new(); + copy->lines[li]->btype = section->lines[li]->btype; + for (c = 0; section->lines[li]->text_above[c]; c++) { + copy->lines[li]->text_above = erealloc(copy->lines[li]->text_above, (c+1) * sizeof(struct ChoLineItemAbove *)); + copy->lines[li]->text_above[c] = cho_text_above_copy(section->lines[li]->text_above[c]); + } + copy->lines[li]->text_above = erealloc(copy->lines[li]->text_above, (c+1) * sizeof(struct ChoLineItemAbove *)); + copy->lines[li]->text_above[c] = NULL; + for (ly = 0; section->lines[li]->items[ly]; ly++) { + copy->lines[li]->items = erealloc(copy->lines[li]->items, (ly+1) * sizeof(struct ChoLineItem *)); + copy->lines[li]->items[ly] = cho_line_item_copy(section->lines[li]->items[ly]); + } + copy->lines[li]->items = erealloc(copy->lines[li]->items, (ly+1) * sizeof(struct ChoLineItem *)); + copy->lines[li]->items[ly] = NULL; + } + copy->lines = erealloc(copy->lines, (li+1) * sizeof(struct ChoLine *)); + copy->lines[li] = NULL; + return copy; +} + +static struct ChoSong * +cho_song_new(void) +{ + struct ChoSong *song = emalloc(sizeof(struct ChoSong)); + song->metadata = NULL; + song->sections = NULL; + song->diagrams = NULL; + memset(song->present_text_types, 0, TT_LENGTH); + return song; +} + +int +cho_song_count(struct ChoSong **songs) +{ + int i; + for (i = 0; songs[i]; i++); + return i; +} + +static const char * +cho_song_get_title(struct ChoSong *song) +{ + struct ChoMetadata **m; + for (m = song->metadata; *m; m++) { + if (!strcmp((*m)->name, "sorttitle")) { + return (*m)->value; + } + } + for (m = song->metadata; *m; m++) { + if (!strcmp((*m)->name, "title")) { + return (*m)->value; + } + } + return NULL; +} + +int +cho_song_compare(const void *a, const void *b) +{ + struct ChoSong *song; + const char *a_title, *b_title; + song = *(struct ChoSong **)a; + a_title = cho_song_get_title(song); + song = *(struct ChoSong **)b; + b_title = cho_song_get_title(song); + return str_compare(a_title, b_title); +} + +static void +cho_song_free(struct ChoSong *song) +{ + if (!song) { + return; + } + struct ChoMetadata **m; + struct ChoSection **se; + struct ChordDiagram **dia; + for (m = song->metadata; *m; m++) { + cho_metadata_free(*m); + } + for (se = song->sections; *se; se++) { + cho_section_free(*se); + } + for (dia = song->diagrams; *dia; dia++) { + chord_diagram_free(*dia); + } + free(song->metadata); + free(song->sections); + free(song->diagrams); + free(song); +} + +void +cho_songs_free(struct ChoSong **songs) +{ + if (!songs) { + return; + } + struct ChoSong **so; + for (so = songs; *so; so++) { + cho_song_free(*so); + } + free(songs); +} + +static struct ChoSection * +cho_find_previous_chorus(struct ChoSection **sections, int se) +{ + int i; + for (i = se; i>=0; i--) { + if (sections[i]->type == ST_CHORUS) { + return sections[i]; + } + } + return NULL; +} + +static struct ChoDirective * +cho_directive_new(void) +{ + struct ChoDirective *directive = emalloc(sizeof(struct ChoDirective)); + directive->dtype = -1; + directive->stype = -1; + directive->position = -1; + directive->sprop = -1; + directive->ttype = -1; + directive->btype = -1; + directive->meta = -1; + directive->ctype = -1; + directive->style = cho_style_new_default(); + return directive; +} + +static void +cho_directive_free(struct ChoDirective *directive) +{ + if (!directive) { + return; + } + cho_style_free(directive->style); + free(directive); +} + +static struct ChoDirective * +cho_directive_parse(const char *name) +{ + struct ChoDirective *directive = cho_directive_new(); + if ( + !strcmp(name, "start_of_chorus") || + !strcmp(name, "soc") + ) { + directive->dtype = DT_ENVIRONMENT; + directive->position = POS_START; + directive->stype = ST_CHORUS; + directive->ttype = TT_CHORUS; + return directive; + } else if ( + !strcmp(name, "end_of_chorus") || + !strcmp(name, "eoc") + ) { + directive->dtype = DT_ENVIRONMENT; + directive->position = POS_END; + directive->stype = ST_CHORUS; + directive->ttype = TT_TEXT; + return directive; + } else if (!strcmp(name, "chorus")) { + directive->dtype = DT_ENVIRONMENT; + directive->position = POS_NO; + directive->ttype = TT_LABEL; + return directive; + } else if ( + !strcmp(name, "start_of_verse") || + !strcmp(name, "sov") + ) { + directive->dtype = DT_ENVIRONMENT; + directive->position = POS_START; + directive->stype = ST_VERSE; + directive->ttype = TT_TEXT; + return directive; + } else if ( + !strcmp(name, "end_of_verse") || + !strcmp(name, "eov") + ) { + directive->dtype = DT_ENVIRONMENT; + directive->position = POS_END; + directive->stype = ST_VERSE; + directive->ttype = TT_TEXT; + return directive; + } else if ( + !strcmp(name, "start_of_bridge") || + !strcmp(name, "sob") + ) { + directive->dtype = DT_ENVIRONMENT; + directive->position = POS_START; + directive->stype = ST_BRIDGE; + directive->ttype = TT_TEXT; + return directive; + } else if ( + !strcmp(name, "end_of_bridge") || + !strcmp(name, "eob") + ) { + directive->dtype = DT_ENVIRONMENT; + directive->position = POS_END; + directive->stype = ST_BRIDGE; + directive->ttype = TT_TEXT; + return directive; + } else if ( + !strcmp(name, "start_of_tab") || + !strcmp(name, "sot") + ) { + directive->dtype = DT_ENVIRONMENT; + directive->position = POS_START; + directive->stype = ST_TAB; + directive->ttype = TT_TAB; + return directive; + } else if ( + !strcmp(name, "end_of_tab") || + !strcmp(name, "eot") + ) { + directive->dtype = DT_ENVIRONMENT; + directive->position = POS_END; + directive->stype = ST_TAB; + directive->ttype = TT_TEXT; + return directive; + } else if ( + !strcmp(name, "start_of_grid") || + !strcmp(name, "sog") + ) { + directive->dtype = DT_ENVIRONMENT; + directive->position = POS_START; + directive->stype = ST_GRID; + directive->ttype = TT_GRID; + return directive; + } else if ( + !strcmp(name, "end_of_grid") || + !strcmp(name, "eog") + ) { + directive->dtype = DT_ENVIRONMENT; + directive->position = POS_END; + directive->stype = ST_GRID; + directive->ttype = TT_TEXT; + return directive; + } + if ( + !strcmp(name, "title") || + !strcmp(name, "t") + ) { + directive->dtype = DT_METADATA; + directive->meta = TITLE; + cho_style_free(directive->style); + g_prev_ttype = g_current_ttype; + g_current_ttype = TT_TITLE; + directive->style = cho_style_new_default(); + g_current_ttype = g_prev_ttype; + return directive; + + } else if ( + !strcmp(name, "subtitle") || + !strcmp(name, "st") + ) { + directive->dtype = DT_METADATA; + directive->meta = SUBTITLE; + cho_style_free(directive->style); + g_prev_ttype = g_current_ttype; + g_current_ttype = TT_SUBTITLE; + directive->style = cho_style_new_default(); + g_current_ttype = g_prev_ttype; + return directive; + } + if ( + !strcmp(name, "sorttitle") || + !strcmp(name, "artist") || + !strcmp(name, "composer") || + !strcmp(name, "lyricist") || + !strcmp(name, "copyright") || + !strcmp(name, "album") || + !strcmp(name, "year") || + !strcmp(name, "key") || + !strcmp(name, "time") || + !strcmp(name, "tempo") || + !strcmp(name, "duration") || + !strcmp(name, "capo") || + !strcmp(name, "meta") || + !strcmp(name, "arranger") + ) { + directive->dtype = DT_METADATA; + return directive; + } + if ( + !strcmp(name, "comment") || + !strcmp(name, "c") || + !strcmp(name, "highlight") + ) { + directive->dtype = DT_FORMATTING; + g_prev_ttype = g_current_ttype; + g_current_ttype = TT_COMMENT; + cho_style_free(directive->style); + directive->style = cho_style_new_default(); + g_current_ttype = g_prev_ttype; + directive->ttype = TT_COMMENT; + return directive; + } else if ( + !strcmp(name, "comment_italic") || + !strcmp(name, "ci") + ) { + directive->dtype = DT_FORMATTING; + g_prev_ttype = g_current_ttype; + g_current_ttype = TT_COMMENT_ITALIC; + cho_style_free(directive->style); + directive->style = cho_style_new_default(); + g_current_ttype = g_prev_ttype; + directive->ttype = TT_COMMENT_ITALIC; + return directive; + } else if ( + !strcmp(name, "comment_box") || + !strcmp(name, "cb") + ) { + directive->dtype = DT_FORMATTING; + g_prev_ttype = g_current_ttype; + g_current_ttype = TT_COMMENT_BOX; + cho_style_free(directive->style); + directive->style = cho_style_new_default(); + g_current_ttype = g_prev_ttype; + directive->ttype = TT_COMMENT_BOX; + return directive; + } + if (!strcmp(name, "image")) { + directive->dtype = DT_IMAGE; + return directive; + } + if ( + !strcmp(name, "new_song") || + !strcmp(name, "ns") + ) { + directive->dtype = DT_PREAMBLE; + directive->stype = ST_NEWSONG; + return directive; + } + if ( + !strcmp(name, "chordfont") || + !strcmp(name, "cf") + ) { + directive->dtype = DT_FONT; + directive->sprop = SPT_FONT; + directive->ttype = TT_CHORD; + return directive; + } else if ( + !strcmp(name, "chordsize") || + !strcmp(name, "cs") + ) { + directive->dtype = DT_FONT; + directive->sprop = SPT_SIZE; + directive->ttype = TT_CHORD; + return directive; + } else if (!strcmp(name, "chordcolour")) { + directive->dtype = DT_FONT; + directive->sprop = SPT_COLOR; + directive->ttype = TT_CHORD; + return directive; + } else if (!strcmp(name, "chorusfont")) { + directive->dtype = DT_FONT; + directive->sprop = SPT_FONT; + directive->ttype = TT_CHORUS; + return directive; + } else if (!strcmp(name, "chorussize")) { + directive->dtype = DT_FONT; + directive->sprop = SPT_SIZE; + directive->ttype = TT_CHORUS; + return directive; + } else if (!strcmp(name, "choruscolour")) { + directive->dtype = DT_FONT; + directive->sprop = SPT_COLOR; + directive->ttype = TT_CHORUS; + return directive; + } else if (!strcmp(name, "gridfont")) { + directive->dtype = DT_FONT; + directive->sprop = SPT_FONT; + directive->ttype = TT_GRID; + return directive; + } else if (!strcmp(name, "gridsize")) { + directive->dtype = DT_FONT; + directive->sprop = SPT_SIZE; + directive->ttype = TT_GRID; + return directive; + } else if (!strcmp(name, "gridcolour")) { + directive->dtype = DT_FONT; + directive->sprop = SPT_COLOR; + directive->ttype = TT_GRID; + return directive; + } else if (!strcmp(name, "tabfont")) { + directive->dtype = DT_FONT; + directive->sprop = SPT_FONT; + directive->ttype = TT_TAB; + return directive; + } else if (!strcmp(name, "tabsize")) { + directive->dtype = DT_FONT; + directive->sprop = SPT_SIZE; + directive->ttype = TT_TAB; + return directive; + } else if (!strcmp(name, "tabcolour")) { + directive->dtype = DT_FONT; + directive->sprop = SPT_COLOR; + directive->ttype = TT_TAB; + return directive; + } else if ( + !strcmp(name, "textfont") || + !strcmp(name, "tf") + ) { + directive->dtype = DT_FONT; + directive->sprop = SPT_FONT; + directive->ttype = TT_TEXT; + return directive; + } else if ( + !strcmp(name, "textsize") || + !strcmp(name, "ts") + ) { + directive->dtype = DT_FONT; + directive->sprop = SPT_SIZE; + directive->ttype = TT_TEXT; + return directive; + } else if (!strcmp(name, "textcolour")) { + directive->dtype = DT_FONT; + directive->sprop = SPT_COLOR; + directive->ttype = TT_TEXT; + return directive; + } else if (!strcmp(name, "titlefont")) { + directive->dtype = DT_FONT; + directive->sprop = SPT_FONT; + directive->ttype = TT_TITLE; + return directive; + } else if (!strcmp(name, "titlesize")) { + directive->dtype = DT_FONT; + directive->sprop = SPT_SIZE; + directive->ttype = TT_TITLE; + return directive; + } else if (!strcmp(name, "titlecolour")) { + directive->dtype = DT_FONT; + directive->sprop = SPT_COLOR; + directive->ttype = TT_TITLE; + return directive; + } else if (!strcmp(name, "tocfont")) { + directive->dtype = DT_FONT; + directive->sprop = SPT_FONT; + directive->ttype = TT_TOC; + } else if (!strcmp(name, "tocsize")) { + directive->dtype = DT_FONT; + directive->sprop = SPT_SIZE; + directive->ttype = TT_TOC; + } else if (!strcmp(name, "toccolour")) { + directive->dtype = DT_FONT; + directive->sprop = SPT_COLOR; + directive->ttype = TT_TOC; + } /* else if (!strcmp(name, "footerfont")) { + } else if (!strcmp(name, "footersize")) { + } else if (!strcmp(name, "footercolour")) { + } else if (!strcmp(name, "labelfont")) { + } else if (!strcmp(name, "labelsize")) { + } else if (!strcmp(name, "labelcolour")) { + } */ + if (!strcmp(name, "transpose")) { + directive->dtype = DT_CHORD; + directive->ctype = TRANSPOSE; + return directive; + } else if (!strcmp(name, "define")) { + directive->dtype = DT_CHORD; + directive->ctype = DEFINE; + return directive; + } /* else if (!strcmp(name, "chord")) { + } */ + if ( + !strcmp(name, "new_page") || + !strcmp(name, "np") + ) { + directive->dtype = DT_OUTPUT; + directive->btype = BT_PAGE; + return directive; + } else if ( + !strcmp(name, "column_break") || + !strcmp(name, "colb") + ) { + directive->dtype = DT_OUTPUT; + directive->btype = BT_COLUMN; + return directive; + } + if (str_starts_with(name, "start_of_")) { + directive->dtype = DT_ENVIRONMENT; + directive->position = POS_START; + directive->stype = ST_CUSTOM; + directive->ttype = TT_TEXT; + return directive; + } else if (str_starts_with(name, "end_of_")) { + directive->dtype = DT_ENVIRONMENT; + directive->position = POS_END; + directive->stype = ST_CUSTOM; + directive->ttype = TT_TEXT; + return directive; + } + if (str_starts_with(name, "x_")) { + directive->dtype = DT_EXTENSION; + return directive; + } + directive->dtype = DT_CUSTOM; + return directive; +} + +static char * +cho_directive_label_parse(const char *directive_name, const char *str) +{ + char *label_name = NULL; + char c; + enum OptionState state = OS_NAME; + enum AttrValueSyntax avs = -1; + char name[5+1]; + char value[URL_MAX_LEN+1]; + int n = 0; + int v = 0; + memset(name, 0, sizeof(name)); + memset(value, 0, sizeof(value)); + int i; + for (i = 0; str[i]; i++) { + c = str[i]; + switch (state) { + case OS_NAME: + if (is_whitespace(c)) { + if (n == 0) { + break; + } else { + name[n] = 0; + cho_log(LOG_ERR, "Option with name '%s' in environment directive '%s' has no value.", name, directive_name); + return NULL; + } + } + if (c == '=') { + name[n] = 0; + if (strcmp(name, "label") != 0) { + cho_log(LOG_ERR, "Invalid option name '%s' in environment directive '%s'.", name, directive_name); + } + memset(name, 0, n); + n = 0; + state = OS_VALUE; + break; + } + if (n > 4) { + cho_log(LOG_ERR, "Option name in environment directive '%s' is too long.", directive_name); + return NULL; + } + name[n] = c; + n++; + break; + case OS_VALUE: + if (avs == -1) { + if (is_whitespace(c)) { + cho_log(LOG_ERR, "Whitespace character after equals sign in environment directive '%s' is invalid.", directive_name); + return NULL; + } + if (c == '\'') { + avs = AVS_APOSTROPHE; + } else if (c == '"') { + avs = AVS_QUOTATION_MARK; + } else { + avs = AVS_UNQUOTED; + value[v] = c; + v++; + } + break; + } + if (c == '\n') { + cho_log(LOG_ERR, "Newline character inside an option value in environment directive '%s' is invalid.", directive_name); + return NULL; + } + if ( + (avs == AVS_APOSTROPHE && c == '\'') || + (avs == AVS_QUOTATION_MARK && c == '"') || + (avs == AVS_UNQUOTED && (c == ' ' || c == '\t')) + ) { + value[v] = 0; + label_name = strdup(value); + memset(value, 0, v); + v = 0; + avs = -1; + state = OS_NAME; + break; + } + value[v] = c; + v++; + break; + } + } + if (avs == AVS_UNQUOTED) { + value[v] = 0; + label_name = strdup(value); + } + return label_name; +} + +struct ChoSong ** +cho_songs_parse(FILE *fp, const char *chordpro_filepath, struct Config *config) +{ + g_config = config; + g_chordpro_filepath = chordpro_filepath; + bool is_chord_already_initialized = false; + bool is_maybe_end_of_tab_directive = false; + bool directive_has_tag = false; + char buf = 0; + char prev_buf = '\n'; + char directive_name[128]; + char directive_value[4096]; + char chord[15]; + char tag_start[6]; + char tag_end[6]; + char custom_directive[64]; + char *label, *metadata_value, *stripped_directive_value; + enum State state = STATE_LYRICS; + enum State state_before_comment = STATE_LYRICS; + enum State state_before_tag = STATE_LYRICS; + enum State state_before_backslash = STATE_LYRICS; + int dn = 0; + int dv = 0; + int ch = 0; + int c = 0; + int m = 0; + int ly = 0; + int t = 0; + int so = 0; + int se = 0; + int li = 0; + int ta = -1; + int te = 0; + int at = 0; + int atn = 0; + int atv = 0; + int ann = 0; + int text_above_pos; + int transpose; + int th = 0; + int dia = 0; + size_t read; + g_transpose_history = erealloc(g_transpose_history, (th+1) * sizeof(int *)); + g_transpose_history[th] = 0; + g_transpose = &g_transpose_history[th]; + th++; + enum AttrValueSyntax avs = -1; + struct ChoDirective *directive = NULL; + struct ChoMetadata *metadata = NULL; + struct ChoLine ***lines; + struct ChoSong **songs = emalloc(sizeof(struct ChoSong *)); + songs[so] = cho_song_new(); + songs[so]->sections = emalloc((se+1) * sizeof(struct ChoSection *)); + songs[so]->sections[se] = cho_section_new(); + songs[so]->sections[se]->lines = emalloc(sizeof(struct ChoLine *)); + songs[so]->sections[se]->lines[li] = cho_line_new(); + lines = &songs[so]->sections[se]->lines; + (*lines)[li]->items = emalloc(sizeof(struct ChoLineItem *)); + (*lines)[li]->items[ly] = cho_line_item_new(); + songs[so]->present_text_types[TT_TOC] = config->output->toc->show; + struct Tag **tags = NULL; + struct ChoStyle *tag_style; + struct StyleProperty sprop; + struct ChoChord *tmp_chord; + struct ChoSection *chorus; + struct ChoImage *image; + struct ChordDiagram *diagram; + while (feof(fp) == 0) { + read = fread(&buf, 1, 1, fp); + if (read == 1) { + if (buf == '\n') { + g_line_number++; + } + // printf("state: %s, buf: %c\n", state_enums[state], buf); + if (buf == '\r') { + continue; + } + switch (state) { + case STATE_LYRICS: { + if (prev_buf == '\n' && buf == '#') { + state_before_comment = STATE_LYRICS; + state = STATE_COMMENT; + break; + } + if (prev_buf == '\n' && buf == '{') { + state = STATE_DIRECTIVE_NAME; + break; + } + if (buf == '[') { + state = STATE_CHORD; + break; + } + if (buf == '<') { + if ((*lines)[li]->items[ly]->is_text) { + (*lines)[li]->items[ly]->u.text->text = erealloc((*lines)[li]->items[ly]->u.text->text, (te+1) * sizeof(char)); + (*lines)[li]->items[ly]->u.text->text[te] = 0; + if (strlen((*lines)[li]->items[ly]->u.text->text) == 0) { + cho_line_item_free((*lines)[li]->items[ly]); + } else { + ly++; + } + } else { + ly++; + } + te = 0; + (*lines)[li]->items = erealloc((*lines)[li]->items, (ly+1) * sizeof(struct ChoLineItem *)); + (*lines)[li]->items[ly] = cho_line_item_new(); + state_before_tag = STATE_LYRICS; + state = STATE_MARKUP_TAG; + break; + } + if (buf == '\n') { + if (prev_buf == '\\') { + state_before_backslash = STATE_LYRICS; + state = STATE_BACKSLASH; + te--; // INFO: This will later overwrite the backslash + break; + } + if (ta > -1 && !tags[ta]->is_closed && strcmp(tags[ta]->name, "img")) { + cho_log(LOG_ERR, "Tag has to be closed on same line."); + return NULL; + } + if ((*lines)[li]->items[ly]->is_text) { + (*lines)[li]->items[ly]->u.text->text = erealloc((*lines)[li]->items[ly]->u.text->text, (te+1) * sizeof(char)); + (*lines)[li]->items[ly]->u.text->text[te] = 0; + if (strlen((*lines)[li]->items[ly]->u.text->text) == 0) { + cho_line_item_free((*lines)[li]->items[ly]); + if (ly == 0) { + if ( + !(*lines)[li]->text_above && + (*lines)[li]->btype == BT_LINE + ) { + free((*lines)[li]->items); + free((*lines)[li]); + *lines = erealloc(*lines, (li+1) * sizeof(struct ChoLine *)); + (*lines)[li] = cho_line_new(); + (*lines)[li]->items = erealloc((*lines)[li]->items, (ly+1) * sizeof(struct ChoLineItem *)); + (*lines)[li]->items[ly] = cho_line_item_new(); + break; + } + } + } else { + ly++; + } + } else { + ly++; + } + (*lines)[li]->items = erealloc((*lines)[li]->items, (ly+1) * sizeof(struct ChoLineItem *)); + (*lines)[li]->items[ly] = NULL; + ly = 0; + te = 0; + (*lines)[li]->text_above = erealloc((*lines)[li]->text_above, (c+1) * sizeof(struct ChoLineItemAbove *)); + (*lines)[li]->text_above[c] = NULL; + c = 0; + li++; + *lines = erealloc(*lines, (li+1) * sizeof(struct ChoLine *)); + (*lines)[li] = cho_line_new(); + (*lines)[li]->items = erealloc((*lines)[li]->items, (ly+1) * sizeof(struct ChoLineItem *)); + (*lines)[li]->items[ly] = cho_line_item_new(); + break; + } + (*lines)[li]->items[ly]->u.text->text = erealloc((*lines)[li]->items[ly]->u.text->text, (te+1) * sizeof(char)); + (*lines)[li]->items[ly]->u.text->text[te] = buf; + te++; + break; + } + case STATE_BACKSLASH: { + if (!is_whitespace(buf)) { + if (fseek(fp, -1, SEEK_CUR)) { + LOG_DEBUG("fseek failed."); + return NULL; + } + state = state_before_backslash; + break; + } + break; + } + case STATE_DIRECTIVE_NAME: { + if (buf == '}') { + directive_name[dn] = 0; + dn = 0; + directive = cho_directive_parse(directive_name); + /* printf( + "directive: '%s'\ndtype: %s, stype: %s, position: %s\n", + directive_name, cho_debug_dtype(directive->dtype), cho_debug_the_stype(directive->stype), cho_debug_the_pos(directive->position) + ); */ + switch (directive->dtype) { + case DT_ENVIRONMENT: { + g_current_ttype = directive->ttype; + switch (directive->position) { + case POS_START: { + if (directive->stype == ST_CUSTOM) { + memset(custom_directive, 0, sizeof(custom_directive)); + strcpy(custom_directive, &directive_name[9]); + } + cho_line_item_free((*lines)[li]->items[ly]); + free((*lines)[li]->items); + ly = 0; + free((*lines)[li]); + (*lines)[li] = NULL; + se++; + songs[so]->sections = erealloc(songs[so]->sections, (se+1) * sizeof(struct ChoSection *)); + songs[so]->sections[se] = cho_section_new(); + songs[so]->sections[se]->type = directive->stype; + li = 0; + lines = &songs[so]->sections[se]->lines; + *lines = emalloc(sizeof(struct ChoLine *)); + (*lines)[li] = cho_line_new(); + (*lines)[li]->items = emalloc(sizeof(struct ChoLineItem *)); + (*lines)[li]->items[ly] = cho_line_item_new(); + songs[so]->present_text_types[directive->ttype] = true; + break; + } + case POS_END: { + if (directive->stype == songs[so]->sections[se]->type) { + if (directive->stype == ST_CUSTOM) { + if (strcmp(custom_directive, &directive_name[7]) != 0) { + break; + } + } + cho_line_item_free((*lines)[li]->items[ly]); + free((*lines)[li]->items); + ly = 0; + free((*lines)[li]); + (*lines)[li] = NULL; + se++; + songs[so]->sections = erealloc(songs[so]->sections, (se+1) * sizeof(struct ChoSection *)); + songs[so]->sections[se] = cho_section_new(); + li = 0; + lines = &songs[so]->sections[se]->lines; + *lines = emalloc(sizeof(struct ChoLine *)); + (*lines)[li] = cho_line_new(); + (*lines)[li]->items = emalloc(sizeof(struct ChoLineItem *)); + (*lines)[li]->items[ly] = cho_line_item_new(); + } + break; + } + case POS_NO: { + /* INFO: {chorus} */ + chorus = cho_find_previous_chorus(songs[so]->sections, se); + if (chorus) { + if (config->output->chorus->quote) { + if ((*lines)[li]->items[ly]->is_text) { + (*lines)[li]->items[ly]->u.text->text = erealloc((*lines)[li]->items[ly]->u.text->text, (te+1) * sizeof(char)); + (*lines)[li]->items[ly]->u.text->text[te] = 0; + } + ly++; + (*lines)[li]->items = erealloc((*lines)[li]->items, (ly+1) * sizeof(struct ChoLineItem *)); + (*lines)[li]->items[ly] = NULL; + ly = 0; + te = 0; + (*lines)[li]->text_above = erealloc((*lines)[li]->text_above, (c+1) * sizeof(struct ChoLineItemAbove *)); + (*lines)[li]->text_above[c] = NULL; + c = 0; + cho_line_free((*lines)[li]); + (*lines)[li] = NULL; + li = 0; + se++; + songs[so]->sections = erealloc(songs[so]->sections, (se+1) * sizeof(struct ChoSection *)); + songs[so]->sections[se] = cho_section_copy(chorus); + se++; + songs[so]->sections = erealloc(songs[so]->sections, (se+1) * sizeof(struct ChoSection *)); + songs[so]->sections[se] = cho_section_new(); + lines = &songs[so]->sections[se]->lines; + *lines = emalloc(sizeof(struct ChoLine *)); + (*lines)[li] = cho_line_new(); + (*lines)[li]->items = emalloc(sizeof(struct ChoLineItem *)); + (*lines)[li]->items[ly] = cho_line_item_new(); + } else { + if (chorus->label) { + label = strdup(chorus->label->text); + } else { + label = strdup(config->output->chorus->label); + } + if ((*lines)[li]->items[ly]->is_text) { + (*lines)[li]->items[ly]->u.text->text = erealloc((*lines)[li]->items[ly]->u.text->text, (te+1) * sizeof(char)); + (*lines)[li]->items[ly]->u.text->text[te] = 0; + if (strlen((*lines)[li]->items[ly]->u.text->text) == 0) { + cho_line_item_free((*lines)[li]->items[ly]); + } else { + ly++; + } + } else { + ly++; + } + (*lines)[li]->items = erealloc((*lines)[li]->items, (ly+1) * sizeof(struct ChoLineItem *)); + (*lines)[li]->items[ly] = cho_line_item_new(); + cho_style_free((*lines)[li]->items[ly]->u.text->style); + g_current_ttype = TT_LABEL; + (*lines)[li]->items[ly]->u.text->style = cho_style_new_default(); + (*lines)[li]->items[ly]->u.text->text = label; + te += strlen(label); + } + } else { + if (config->output->chorus->quote) { + cho_log(LOG_WARN, "Can't quote chorus because it's not defined previously."); + } + label = strdup(config->output->chorus->label); + if ((*lines)[li]->items[ly]->is_text) { + (*lines)[li]->items[ly]->u.text->text = erealloc((*lines)[li]->items[ly]->u.text->text, (te+1) * sizeof(char)); + (*lines)[li]->items[ly]->u.text->text[te] = 0; + if (strlen((*lines)[li]->items[ly]->u.text->text) == 0) { + cho_line_item_free((*lines)[li]->items[ly]); + } else { + ly++; + } + } else { + ly++; + } + (*lines)[li]->items = erealloc((*lines)[li]->items, (ly+1) * sizeof(struct ChoLineItem *)); + (*lines)[li]->items[ly] = cho_line_item_new(); + cho_style_free((*lines)[li]->items[ly]->u.text->style); + g_current_ttype = TT_LABEL; + (*lines)[li]->items[ly]->u.text->style = cho_style_new_default(); + (*lines)[li]->items[ly]->u.text->text = label; + te += strlen(label); + } + break; + } + default: + cho_log(LOG_ERR, "Invalid position value '%d'.", directive->position); + return NULL; + } + break; + } + case DT_METADATA: + cho_log(LOG_WARN, "Ignoring metadata directive '%s' because it has no value.", directive_name); + break; + case DT_FORMATTING: + cho_log(LOG_WARN, "Formatting directive '%s' has no value.", directive_name); + break; + case DT_IMAGE: + cho_log(LOG_ERR, "Directive 'image' has no value."); + return NULL; + case DT_PREAMBLE: { + // INFO: The only preamble directive is 'new_song' + cho_line_item_free((*lines)[li]->items[ly]); + free((*lines)[li]->items); + free((*lines)[li]); + (*lines)[li] = NULL; + songs[so]->metadata = erealloc(songs[so]->metadata, (m+1) * sizeof(struct ChoMetadata *)); + songs[so]->metadata[m] = NULL; + se++; + songs[so]->sections = erealloc(songs[so]->sections, (se+1) * sizeof(struct ChoSection *)); + songs[so]->sections[se] = NULL; + songs[so]->diagrams = erealloc(songs[so]->diagrams, (dia+1) * sizeof(struct ChordDiagram *)); + songs[so]->diagrams[dia] = NULL; + if (!cho_style_reset_default()) { + LOG_DEBUG("cho_style_reset_default failed."); + return NULL; + } + so++; + songs = erealloc(songs, (so+1) * sizeof(struct ChoSong *)); + songs[so] = cho_song_new(); + se = 0; + li = 0; + ly = 0; + m = 0; + dia = 0; + songs[so]->sections = emalloc((se+1) * sizeof(struct ChoSection *)); + songs[so]->sections[se] = cho_section_new(); + lines = &songs[so]->sections[se]->lines; + *lines = erealloc(*lines, (li+1) * sizeof(struct ChoLine *)); + (*lines)[li] = cho_line_new(); + (*lines)[li]->items = emalloc(sizeof(struct ChoLineItem *)); + (*lines)[li]->items[ly] = cho_line_item_new(); + break; + } + case DT_FONT: { + sprop.ttype = directive->ttype; + sprop.type = directive->sprop; + switch (directive->sprop) { + case SPT_FONT: + sprop.u.font_name = NULL; + break; + case SPT_SIZE: + sprop.u.font_size = EMPTY_DOUBLE; + break; + case SPT_COLOR: + sprop.u.foreground_color = NULL; + break; + default: + cho_log(LOG_ERR, "Invalid style property type '%d'.", directive->sprop); + return NULL; + } + if (!cho_style_change_default(sprop)) { + LOG_DEBUG("cho_style_change_default failed."); + return NULL; + } + break; + } + case DT_CHORD: { + switch (directive->ctype) { + case TRANSPOSE: + g_transpose--; + th--; + break; + case DEFINE: + cho_log(LOG_WARN, "Ignoring chord directive '%s' because it has no value.", directive_name); + break; + } + break; + } + case DT_OUTPUT: + if (directive->btype != -1) { + (*lines)[li]->btype = directive->btype; + } + break; + case DT_EXTENSION: + // INFO: Such a directive should not be logged. + break; + case DT_CUSTOM: + cho_log(LOG_INFO, "Ignoring custom directive '%s'.", directive_name); + break; + default: + cho_log(LOG_ERR, "Invalid directive '%s'.", directive_name); + return NULL; + } + if (directive->stype == ST_TAB) { + state = STATE_TAB; + } else { + state = STATE_LYRICS; + } + cho_directive_free(directive); + directive = NULL; + break; + } + if (buf == '{') { + cho_log(LOG_ERR, "Can't start a new directive if the previous one is not yet closed."); + return NULL; + } + if (buf == '\n') { + if (prev_buf == '\\') { + state_before_backslash = STATE_DIRECTIVE_NAME; + state = STATE_BACKSLASH; + dn--; + break; + } + cho_log(LOG_ERR, "Can't have a newline in a directive name."); + return NULL; + } + if (buf == ':' || buf == ' ') { + directive_name[dn] = 0; + dn = 0; + state = STATE_DIRECTIVE_VALUE; + break; + } + directive_name[dn] = buf; + dn++; + break; + } + case STATE_DIRECTIVE_VALUE: { + if (buf == '}') { + directive_value[dv] = 0; + dv = 0; + stripped_directive_value = str_remove_leading_whitespace(directive_value); + directive = cho_directive_parse(directive_name); + /* printf( + "directive: '%s'\ndtype: %s, stype: %s, position: %s\n", + directive_name, dtype(directive->dtype), the_stype(directive->stype), pos(directive->position) + ); */ + switch (directive->dtype) { + case DT_ENVIRONMENT: { + if (strlen(stripped_directive_value) > 0) { + songs[so]->present_text_types[TT_LABEL] = true; + } + g_current_ttype = directive->ttype; + switch (directive->position) { + case POS_START: { + if (directive->stype == ST_CUSTOM) { + memset(custom_directive, 0, sizeof(custom_directive)); + strcpy(custom_directive, &directive_name[9]); + } + cho_line_item_free((*lines)[li]->items[ly]); + free((*lines)[li]->items); + ly = 0; + free((*lines)[li]); + (*lines)[li] = NULL; + se++; + songs[so]->sections = erealloc(songs[so]->sections, (se+1) * sizeof(struct ChoSection *)); + songs[so]->sections[se] = cho_section_new(); + songs[so]->sections[se]->type = directive->stype; + songs[so]->sections[se]->label = emalloc(sizeof(struct ChoText)); + if (strstr(directive_value, "=")) { + label = cho_directive_label_parse(directive_name, directive_value); + if (!label) { + LOG_DEBUG("cho_directive_label_parse failed."); + cho_log(LOG_ERR, "Failed to parse the section label. You have to ways of specifying a label:\n\t\t\t1. {start_of_*: label=\"Label name\"}\n\t\t\t2. {start_of*: Label name}"); + return NULL; + } + songs[so]->sections[se]->label->text = label; + } else { + songs[so]->sections[se]->label->text = strdup(stripped_directive_value); + } + songs[so]->sections[se]->label->style = cho_style_new_from_config(TT_LABEL); + if (directive_has_tag) { + cho_style_complement(songs[so]->sections[se]->label->style, tags[ta]->style, &tags[ta]->style_presence); + directive_has_tag = false; + } + li = 0; + lines = &songs[so]->sections[se]->lines; + *lines = emalloc(sizeof(struct ChoLine *)); + (*lines)[li] = cho_line_new(); + (*lines)[li]->items = emalloc(sizeof(struct ChoLineItem *)); + (*lines)[li]->items[ly] = cho_line_item_new(); + songs[so]->present_text_types[directive->ttype] = true; + break; + } + case POS_END: { + if (directive->stype == songs[so]->sections[se]->type) { + cho_line_item_free((*lines)[li]->items[ly]); + free((*lines)[li]->items); + ly = 0; + free((*lines)[li]); + (*lines)[li] = NULL; + se++; + songs[so]->sections = erealloc(songs[so]->sections, (se+1) * sizeof(struct ChoSection *)); + songs[so]->sections[se] = cho_section_new(); + li = 0; + lines = &songs[so]->sections[se]->lines; + *lines = emalloc(sizeof(struct ChoLine *)); + (*lines)[li] = cho_line_new(); + (*lines)[li]->items = emalloc(sizeof(struct ChoLineItem *)); + (*lines)[li]->items[ly] = cho_line_item_new(); + } + break; + } + case POS_NO: { + /* INFO: {chorus: ...} */ + label = strdup(stripped_directive_value); + chorus = cho_find_previous_chorus(songs[so]->sections, se); + if (chorus) { + if (config->output->chorus->quote) { + if ((*lines)[li]->items[ly]->is_text) { + (*lines)[li]->items[ly]->u.text->text = erealloc((*lines)[li]->items[ly]->u.text->text, (te+1) * sizeof(char)); + (*lines)[li]->items[ly]->u.text->text[te] = 0; + } + ly++; + (*lines)[li]->items = erealloc((*lines)[li]->items, (ly+1) * sizeof(struct ChoLineItem *)); + (*lines)[li]->items[ly] = NULL; + ly = 0; + te = 0; + (*lines)[li]->text_above = erealloc((*lines)[li]->text_above, (c+1) * sizeof(struct ChoLineItemAbove *)); + (*lines)[li]->text_above[c] = NULL; + c = 0; + cho_line_free((*lines)[li]); + // songs[so]->sections[se]->lines = erealloc(songs[so]->sections[se]->lines, (li+1) * sizeof(struct ChoLine *)); + (*lines)[li] = NULL; + li = 0; + se++; + songs[so]->sections = erealloc(songs[so]->sections, (se+1) * sizeof(struct ChoSection *)); + songs[so]->sections[se] = cho_section_copy(chorus); + if (songs[so]->sections[se]->label) { + free(songs[so]->sections[se]->label->text); + songs[so]->sections[se]->label->text = label; + } else { + g_current_ttype = TT_LABEL; + songs[so]->sections[se]->label = cho_text_new(); + songs[so]->sections[se]->label->text = label; + } + if (directive_has_tag) { + cho_style_complement(songs[so]->sections[se]->label->style, tags[ta]->style, &tags[ta]->style_presence); + directive_has_tag = false; + } + se++; + songs[so]->sections = erealloc(songs[so]->sections, (se+1) * sizeof(struct ChoSection *)); + songs[so]->sections[se] = cho_section_new(); + lines = &songs[so]->sections[se]->lines; + *lines = emalloc(sizeof(struct ChoLine *)); + (*lines)[li] = cho_line_new(); + (*lines)[li]->items = emalloc(sizeof(struct ChoLineItem *)); + (*lines)[li]->items[ly] = cho_line_item_new(); + } else { + if ((*lines)[li]->items[ly]->is_text) { + (*lines)[li]->items[ly]->u.text->text = erealloc((*lines)[li]->items[ly]->u.text->text, (te+1) * sizeof(char)); + (*lines)[li]->items[ly]->u.text->text[te] = 0; + if (strlen((*lines)[li]->items[ly]->u.text->text) == 0) { + cho_line_item_free((*lines)[li]->items[ly]); + } else { + ly++; + } + } else { + ly++; + } + (*lines)[li]->items = erealloc((*lines)[li]->items, (ly+1) * sizeof(struct ChoLineItem *)); + (*lines)[li]->items[ly] = cho_line_item_new(); + cho_style_free((*lines)[li]->items[ly]->u.text->style); + g_current_ttype = TT_LABEL; + (*lines)[li]->items[ly]->u.text->style = cho_style_new_default(); + (*lines)[li]->items[ly]->u.text->text = label; + if (directive_has_tag) { + cho_style_complement((*lines)[li]->items[ly]->u.text->style, tags[ta]->style, &tags[ta]->style_presence); + directive_has_tag = false; + } + te += strlen(label); + } + } else { + if (config->output->chorus->quote) { + cho_log(LOG_WARN, "Can't quote chorus because it's not defined previously."); + } + if ((*lines)[li]->items[ly]->is_text) { + (*lines)[li]->items[ly]->u.text->text = erealloc((*lines)[li]->items[ly]->u.text->text, (te+1) * sizeof(char)); + (*lines)[li]->items[ly]->u.text->text[te] = 0; + if (strlen((*lines)[li]->items[ly]->u.text->text) == 0) { + cho_line_item_free((*lines)[li]->items[ly]); + } else { + ly++; + } + } else { + ly++; + } + (*lines)[li]->items = erealloc((*lines)[li]->items, (ly+1) * sizeof(struct ChoLineItem *)); + (*lines)[li]->items[ly] = cho_line_item_new(); + cho_style_free((*lines)[li]->items[ly]->u.text->style); + g_current_ttype = TT_LABEL; + (*lines)[li]->items[ly]->u.text->style = cho_style_new_default(); + (*lines)[li]->items[ly]->u.text->text = label; + if (directive_has_tag) { + cho_style_complement((*lines)[li]->items[ly]->u.text->style, tags[ta]->style, &tags[ta]->style_presence); + } + te += strlen(label); + } + break; + } + default: + cho_log(LOG_ERR, "Invalid position value '%d'.", directive->position); + return NULL; + } + break; + } + case DT_METADATA: { + metadata_value = strdup(stripped_directive_value); + if (strlen(metadata_value) == 0) { + cho_log(LOG_WARN, "Ignoring metadata directive '%s' because it has no value.", directive_name); + free(metadata_value); + break; + } + switch (directive->meta) { + case TITLE: + songs[so]->metadata = erealloc(songs[so]->metadata, (m+1) * sizeof(struct ChoMetadata *)); + songs[so]->metadata[m] = cho_metadata_new(); + songs[so]->metadata[m]->name = strdup("title"); + songs[so]->metadata[m]->value = metadata_value; + cho_style_free(songs[so]->metadata[m]->style); + songs[so]->metadata[m]->style = cho_style_copy(directive->style); + songs[so]->present_text_types[TT_TITLE] = true; + break; + case SUBTITLE: + songs[so]->metadata = erealloc(songs[so]->metadata, (m+1) * sizeof(struct ChoMetadata *)); + songs[so]->metadata[m] = cho_metadata_new(); + songs[so]->metadata[m]->name = strdup("subtitle"); + songs[so]->metadata[m]->value = metadata_value; + cho_style_free(songs[so]->metadata[m]->style); + songs[so]->metadata[m]->style = cho_style_copy(directive->style); + songs[so]->present_text_types[TT_SUBTITLE] = true; + break; + default: + if (!strcmp(directive_name, "meta")) { + metadata = cho_metadata_split(directive_value); + if (!metadata) { + LOG_DEBUG("cho_metadata_split failed."); + return NULL; + } + songs[so]->metadata = erealloc(songs[so]->metadata, (m+1) * sizeof(struct ChoMetadata *)); + songs[so]->metadata[m] = metadata; + free(metadata_value); + } else { + songs[so]->metadata = erealloc(songs[so]->metadata, (m+1) * sizeof(struct ChoMetadata *)); + songs[so]->metadata[m] = cho_metadata_new(); + songs[so]->metadata[m]->name = strdup(directive_name); + songs[so]->metadata[m]->value = metadata_value; + } + } + if (directive_has_tag) { + cho_style_complement(songs[so]->metadata[m]->style, tags[ta]->style, &tags[ta]->style_presence); + directive_has_tag = false; + } + m++; + break; + } + case DT_FORMATTING: { + if ((*lines)[li]->items[ly]->is_text) { + (*lines)[li]->items[ly]->u.text->text = erealloc((*lines)[li]->items[ly]->u.text->text, (te+1) * sizeof(char)); + (*lines)[li]->items[ly]->u.text->text[te] = 0; + if (strlen((*lines)[li]->items[ly]->u.text->text) == 0) { + cho_line_item_free((*lines)[li]->items[ly]); + } else { + ly++; + } + } else { + ly++; + } + (*lines)[li]->items = erealloc((*lines)[li]->items, (ly+1) * sizeof(struct ChoLineItem *)); + (*lines)[li]->items[ly] = cho_line_item_new(); + cho_style_free((*lines)[li]->items[ly]->u.text->style); + (*lines)[li]->items[ly]->u.text->style = cho_style_copy(directive->style); + (*lines)[li]->items[ly]->u.text->text = strdup(stripped_directive_value); + if (directive_has_tag) { + cho_style_complement((*lines)[li]->items[ly]->u.text->style, tags[ta]->style, &tags[ta]->style_presence); + directive_has_tag = false; + } + te += strlen(stripped_directive_value); + songs[so]->present_text_types[directive->ttype] = true; + break; + } + case DT_IMAGE: { + if (strstr(directive_value, "=")) { + image = cho_image_directive_parse(directive_value); + if (!image) { + LOG_DEBUG("cho_image_directive_parse failed."); + return NULL; + } + } else { + image = cho_image_new(); + image->src = strdup(stripped_directive_value); + } + if (image->is_asset) { + g_image_assets = erealloc(g_image_assets, (g_ia+1) * sizeof(struct ChoImage *)); + g_image_assets[g_ia] = image; + g_ia++; + } else { + if ((*lines)[li]->items[ly]->is_text) { + (*lines)[li]->items[ly]->u.text->text = erealloc((*lines)[li]->items[ly]->u.text->text, (te+1) * sizeof(char)); + (*lines)[li]->items[ly]->u.text->text[te] = 0; + te = 0; + } + ly++; + (*lines)[li]->items = erealloc((*lines)[li]->items, (ly+1) * sizeof(struct ChoLineItem *)); + (*lines)[li]->items[ly] = cho_line_item_new(); + cho_text_free((*lines)[li]->items[ly]->u.text); + (*lines)[li]->items[ly]->is_text = false; + (*lines)[li]->items[ly]->u.image = image; + } + break; + } + case DT_PREAMBLE: + cho_log(LOG_ERR, "Preamble directive '%s' can't have a value.", directive_name); + return NULL; + case DT_FONT: { + sprop.ttype = directive->ttype; + char *dir_value = strdup(stripped_directive_value); + switch (directive->sprop) { + case SPT_FONT: + sprop.u.font_name = emalloc((strlen(dir_value)+1) * sizeof(char)); + strcpy(sprop.u.font_name, dir_value); + sprop.type = SPT_FONT; + break; + case SPT_SIZE: + sprop.u.font_size = strtod(dir_value, NULL); + if (sprop.u.font_size == 0.0) { + cho_log(LOG_ERR, "Font directive '%s' has an invalid value.", directive_name); + return NULL; + } + sprop.type = SPT_SIZE; + break; + case SPT_COLOR: + sprop.u.foreground_color = cho_color_parse(dir_value); + if (sprop.u.foreground_color == NULL) { + cho_log(LOG_ERR, "Font directive '%s' has an invalid value.", directive_name); + return NULL; + } + sprop.type = SPT_COLOR; + break; + default: + cho_log(LOG_ERR, "Invalid style property type '%d'.", directive->sprop); + return NULL; + } + if (!cho_style_change_default(sprop)) { + LOG_DEBUG("cho_style_change_default failed."); + return NULL; + } + if (sprop.type == SPT_FONT) { + free(sprop.u.font_name); + } else if (sprop.type == SPT_COLOR) { + free(sprop.u.foreground_color); + } + free(dir_value); + break; + } + case DT_CHORD: { + switch (directive->ctype) { + case TRANSPOSE: + if (!transposition_parse(directive_value, &transpose)) { + LOG_DEBUG("transposition_parse failed."); + cho_log(LOG_ERR, "Directive 'transpose' has an invalid value."); + return NULL; + } + g_transpose_history = erealloc(g_transpose_history, (th+1) * sizeof(int *)); + g_transpose_history[th] = g_transpose_history[th-1] + transpose; + g_transpose = &g_transpose_history[th]; + th++; + break; + case DEFINE: + diagram = cho_chord_diagram_parse(directive_value, songs[so]->diagrams, dia); + if (!diagram) { + LOG_DEBUG("cho_chord_diagram_parse failed."); + return NULL; + } + songs[so]->diagrams = erealloc(songs[so]->diagrams, (dia+1) * sizeof(struct ChordDiagram *)); + songs[so]->diagrams[dia] = diagram; + dia++; + break; + } + break; + } + case DT_OUTPUT: + cho_log(LOG_ERR, "Directive '%s' can't have a value.", directive_name); + return NULL; + case DT_EXTENSION: + // INFO: Such a directive should not be logged. + break; + case DT_CUSTOM: + cho_log(LOG_INFO, "Ignoring custom directive '%s'.", directive_name); + break; + default: + cho_log(LOG_ERR, "Invalid directive type '%d'.", directive->dtype); + return NULL; + } + memset(directive_value, 0, strlen(directive_value)); + free(stripped_directive_value); + cho_directive_free(directive); + directive = NULL; + state = STATE_LYRICS; + break; + } + if (buf == '<') { + state_before_tag = STATE_DIRECTIVE_VALUE; + state = STATE_MARKUP_TAG; + break; + } + if (buf == '{') { + cho_log(LOG_ERR, "Can't start a new directive if the previous one is not yet closed."); + return NULL; + } + if (buf == '\n') { + if (prev_buf == '\\') { + state_before_backslash = STATE_DIRECTIVE_VALUE; + state = STATE_BACKSLASH; + dv--; + break; + } + cho_log(LOG_ERR, "Newline character inside a directive value is invalid."); + return NULL; + } + if (dv > 4094) { + cho_log(LOG_ERR, "Directive value can't be greater than 4095 bytes."); + return NULL; + } + directive_value[dv] = buf; + dv++; + break; + } + case STATE_CHORD: { + if (buf == ']') { + chord[ch] = 0; + ch = 0; + g_prev_ttype = g_current_ttype; + g_current_ttype = TT_CHORD; + if (is_chord_already_initialized) { + text_above_pos = cho_line_compute_text_above_position(songs[so]->sections[se]->lines[li], ly, te); + (*lines)[li]->text_above[c]->position = text_above_pos; + tmp_chord = cho_chord_parse(chord); + cho_chord_complete((*lines)[li]->text_above[c]->u.chord, tmp_chord); + if (!(*lines)[li]->text_above[c]->u.chord->is_canonical) { + cho_log(LOG_INFO, "Didn't recognize the chord '%s'.", (*lines)[li]->text_above[c]->u.chord->name); + } + cho_chord_free(tmp_chord); + is_chord_already_initialized = false; + } else { + (*lines)[li]->text_above = erealloc((*lines)[li]->text_above, (c+1) * sizeof(struct ChoLineItemAbove *)); + (*lines)[li]->text_above[c] = emalloc(sizeof(struct ChoLineItemAbove)); + (*lines)[li]->text_above[c]->is_chord = true; + text_above_pos = cho_line_compute_text_above_position((*lines)[li], ly, te); + (*lines)[li]->text_above[c]->position = text_above_pos; + (*lines)[li]->text_above[c]->u.chord = cho_chord_parse(chord); + if (!(*lines)[li]->text_above[c]->u.chord->is_canonical) { + cho_log(LOG_INFO, "Didn't recognize the chord '%s'.", (*lines)[li]->text_above[c]->u.chord->name); + } + } + songs[so]->present_text_types[TT_CHORD] = true; + memset(chord, 0, strlen(chord)); + c++; + g_current_ttype = g_prev_ttype; + state = STATE_LYRICS; + break; + } + if (prev_buf == '[' && buf == '*') { + g_prev_ttype = g_current_ttype; + g_current_ttype = TT_ANNOT; + (*lines)[li]->text_above = erealloc((*lines)[li]->text_above, (c+1) * sizeof(struct ChoLineItemAbove *)); + (*lines)[li]->text_above[c] = emalloc(sizeof(struct ChoLineItemAbove)); + (*lines)[li]->text_above[c]->is_chord = false; + text_above_pos = cho_line_compute_text_above_position((*lines)[li], ly, te); + (*lines)[li]->text_above[c]->position = text_above_pos; + (*lines)[li]->text_above[c]->u.annot = cho_text_new(); + state = STATE_ANNOTATION; + break; + } + if (buf == '\n') { + if (prev_buf == '\\') { + state_before_backslash = STATE_CHORD; + state = STATE_BACKSLASH; + ch--; + break; + } + cho_log(LOG_ERR, "Newline character inside a chord is invalid."); + return NULL; + } + if (buf == '[') { + cho_log(LOG_ERR, "Can't start a new chord/annotation if the previous one is not yet closed."); + return NULL; + } + if (buf == '<') { + if (prev_buf == '[') { + (*lines)[li]->text_above = erealloc((*lines)[li]->text_above, (c+1) * sizeof(struct ChoLineItemAbove *)); + (*lines)[li]->text_above[c] = emalloc(sizeof(struct ChoLineItemAbove)); + (*lines)[li]->text_above[c]->is_chord = true; + (*lines)[li]->text_above[c]->u.chord = cho_chord_new(); + is_chord_already_initialized = true; + } + state_before_tag = STATE_CHORD; + state = STATE_MARKUP_TAG; + break; + } + chord[ch] = buf; + ch++; + break; + } + case STATE_ANNOTATION: { + if (buf == ']') { + (*lines)[li]->text_above[c]->u.annot->text = erealloc((*lines)[li]->text_above[c]->u.annot->text, (ann+1) * sizeof(char)); + (*lines)[li]->text_above[c]->u.annot->text[ann] = 0; + songs[so]->present_text_types[TT_ANNOT] = true; + ann = 0; + c++; + g_current_ttype = g_prev_ttype; + state = STATE_LYRICS; + break; + } + if (buf == '<') { + state_before_tag = STATE_ANNOTATION; + state = STATE_MARKUP_TAG; + break; + } + if (buf == '\n') { + if (prev_buf == '\\') { + state_before_backslash = STATE_ANNOTATION; + state = STATE_BACKSLASH; + ann--; + break; + } + cho_log(LOG_ERR, "Newline character inside an annotation is invalid."); + return NULL; + } + (*lines)[li]->text_above[c]->u.annot->text = erealloc((*lines)[li]->text_above[c]->u.annot->text, (ann+1) * sizeof(char)); + (*lines)[li]->text_above[c]->u.annot->text[ann] = buf; + ann++; + break; + } + case STATE_TAB: { + // INFO: similar to STATE_LYRICS but without markup and directives + if (prev_buf == '\n' && buf == '#') { + state_before_comment = STATE_TAB; + state = STATE_COMMENT; + break; + } + if (is_maybe_end_of_tab_directive) { + if (buf == '}') { + directive_name[dn] = 0; + dn = 0; + is_maybe_end_of_tab_directive = false; + if (!strcmp(directive_name, "end_of_tab") || !strcmp(directive_name, "eot")) { + cho_line_item_free((*lines)[li]->items[ly]); + free((*lines)[li]->items); + ly = 0; + free((*lines)[li]); + (*lines)[li] = NULL; + se++; + songs[so]->sections = erealloc(songs[so]->sections, (se+1) * sizeof(struct ChoSection *)); + songs[so]->sections[se] = cho_section_new(); + li = 0; + lines = &songs[so]->sections[se]->lines; + *lines = emalloc(sizeof(struct ChoLine *)); + (*lines)[li] = cho_line_new(); + (*lines)[li]->items = emalloc(sizeof(struct ChoLineItem *)); + (*lines)[li]->items[ly] = cho_line_item_new(); + g_current_ttype = TT_TEXT; + state = STATE_LYRICS; + break; + } else { + (*lines)[li]->items[ly]->u.text->text = erealloc((*lines)[li]->items[ly]->u.text->text, (te+1) * sizeof(char)); + (*lines)[li]->items[ly]->u.text->text[te] = '{'; + te++; + char *k; + for (k = (char *)&directive_name; *k; k++, te++) { + (*lines)[li]->items[ly]->u.text->text = erealloc((*lines)[li]->items[ly]->u.text->text, (te+1) * sizeof(char)); + (*lines)[li]->items[ly]->u.text->text[te] = *k; + } + (*lines)[li]->items[ly]->u.text->text = erealloc((*lines)[li]->items[ly]->u.text->text, (te+1) * sizeof(char)); + (*lines)[li]->items[ly]->u.text->text[te] = '}'; + te++; + } + break; + } + if (buf == ' ' || buf == ':') { + directive_name[dn] = 0; + dn = 0; + is_maybe_end_of_tab_directive = false; + (*lines)[li]->items[ly]->u.text->text = erealloc((*lines)[li]->items[ly]->u.text->text, (te+1) * sizeof(char)); + (*lines)[li]->items[ly]->u.text->text[te] = '{'; + te++; + char *k; + for (k = (char *)&directive_name; *k; k++, te++) { + (*lines)[li]->items[ly]->u.text->text = erealloc((*lines)[li]->items[ly]->u.text->text, (te+1) * sizeof(char)); + (*lines)[li]->items[ly]->u.text->text[te] = *k; + } + (*lines)[li]->items[ly]->u.text->text = erealloc((*lines)[li]->items[ly]->u.text->text, (te+1) * sizeof(char)); + (*lines)[li]->items[ly]->u.text->text[te] = buf; + te++; + break; + } + directive_name[dn] = buf; + dn++; + break; + } + if (prev_buf == '\n' && buf == '{') { + is_maybe_end_of_tab_directive = true; + break; + } + if (buf == '\n') { + if (prev_buf == '\\') { + state_before_backslash = STATE_TAB; + state = STATE_BACKSLASH; + // INFO: This will later overwrite the backslash + te--; + break; + } + if (ta > -1 && !tags[ta]->is_closed && strcmp(tags[ta]->name, "img")) { + cho_log(LOG_ERR, "Tag has to be closed on same line."); + return NULL; + } + if ((*lines)[li]->items[ly]->is_text) { + (*lines)[li]->items[ly]->u.text->text = erealloc((*lines)[li]->items[ly]->u.text->text, (te+1) * sizeof(char)); + (*lines)[li]->items[ly]->u.text->text[te] = 0; + if (strlen((*lines)[li]->items[ly]->u.text->text) == 0) { + cho_line_item_free((*lines)[li]->items[ly]); + if (ly == 0) { + if ( + !(*lines)[li]->text_above && + (*lines)[li]->btype == BT_LINE + ) { + free((*lines)[li]->items); + free((*lines)[li]); + *lines = erealloc(*lines, (li+1) * sizeof(struct ChoLine *)); + (*lines)[li] = cho_line_new(); + (*lines)[li]->items = erealloc((*lines)[li]->items, (ly+1) * sizeof(struct ChoLineItem *)); + (*lines)[li]->items[ly] = cho_line_item_new(); + break; + } + } + } else { + ly++; + } + } else { + ly++; + } + (*lines)[li]->items = erealloc((*lines)[li]->items, (ly+1) * sizeof(struct ChoLineItem *)); + (*lines)[li]->items[ly] = NULL; + ly = 0; + te = 0; + (*lines)[li]->text_above = erealloc((*lines)[li]->text_above, (c+1) * sizeof(struct ChoLineItemAbove *)); + (*lines)[li]->text_above[c] = NULL; + c = 0; + li++; + *lines = erealloc(*lines, (li+1) * sizeof(struct ChoLine *)); + (*lines)[li] = cho_line_new(); + (*lines)[li]->items = erealloc((*lines)[li]->items, (ly+1) * sizeof(struct ChoLineItem *)); + (*lines)[li]->items[ly] = cho_line_item_new(); + break; + } + (*lines)[li]->items[ly]->u.text->text = erealloc((*lines)[li]->items[ly]->u.text->text, (te+1) * sizeof(char)); + (*lines)[li]->items[ly]->u.text->text[te] = buf; + te++; + break; + } + case STATE_MARKUP_TAG: { + if (buf == '/') { + state = STATE_MARKUP_TAG_END; + break; + } + ta++; + tags = erealloc(tags, (ta+1) * sizeof(struct Tag *)); + tags[ta] = cho_tag_new(); + state = STATE_MARKUP_TAG_START; + if (fseek(fp, -1, SEEK_CUR)) { + LOG_DEBUG("fseek failed."); + return NULL; + } + break; + } + case STATE_MARKUP_TAG_START: { + if (buf == '>') { + tag_start[t] = 0; + t = 0; + if (!strcmp(tag_start, "img")) { + cho_log(LOG_ERR, "'img' tag has to have at least the 'src' attribute."); + return NULL; + } + tags[ta]->name = strdup(tag_start); + tag_style = cho_style_parse(tag_start, NULL, cho_tag_style_inherit(tags, ta-1), &tags[ta]->style_presence); + if (!tag_style) { + LOG_DEBUG("cho_style_parse failed."); + return NULL; + } + tags[ta]->style = tag_style; + switch (state_before_tag) { + case STATE_LYRICS: + cho_style_free((*lines)[li]->items[ly]->u.text->style); + (*lines)[li]->items[ly]->u.text->style = cho_style_copy(tag_style); + break; + case STATE_CHORD: + cho_style_free((*lines)[li]->text_above[c]->u.chord->style); + (*lines)[li]->text_above[c]->u.chord->style = cho_style_copy(tag_style); + break; + case STATE_ANNOTATION: + if (ann > 0) { + (*lines)[li]->text_above[c]->u.annot->text = erealloc((*lines)[li]->text_above[c]->u.annot->text, (ann+1) * sizeof(char)); + (*lines)[li]->text_above[c]->u.annot->text[ann] = 0; + ann = 0; + c++; + (*lines)[li]->text_above = erealloc((*lines)[li]->text_above, (c+1) * sizeof(struct ChoLineItemAbove *)); + (*lines)[li]->text_above[c] = emalloc(sizeof(struct ChoLineItemAbove)); + (*lines)[li]->text_above[c]->is_chord = false; + (*lines)[li]->text_above[c]->position = text_above_pos; + (*lines)[li]->text_above[c]->u.annot = cho_text_new(); + } + cho_style_free((*lines)[li]->text_above[c]->u.annot->style); + (*lines)[li]->text_above[c]->u.annot->style = cho_style_copy(tag_style); + break; + case STATE_DIRECTIVE_VALUE: + directive_has_tag = true; + break; + default: + cho_log(LOG_ERR, "Invalid state_before_tag '%s'.", state_enums[state_before_tag]); + return NULL; + } + memset(tag_start, 0, strlen(tag_start)); + state = state_before_tag; + break; + } + if (is_whitespace(buf)) { + tag_start[t] = 0; + t = 0; + tags[ta]->name = strdup(tag_start); + tags[ta]->attrs = erealloc(tags[ta]->attrs, (at+1) * sizeof(struct Attr *)); + tags[ta]->attrs[at] = cho_tag_attr_new(); + state = STATE_MARKUP_ATTR_NAME; + break; + } + if (buf == '\n') { + if (prev_buf == '\\') { + state_before_backslash = STATE_MARKUP_TAG_START; + state = STATE_BACKSLASH; + t--; + break; + } + cho_log(LOG_ERR, "Newline character inside a tag name is invalid."); + return NULL; + } + if (t == 5) { + cho_log(LOG_ERR, "Start tag name is too long."); + return NULL; + } + tag_start[t] = buf; + t++; + break; + } + case STATE_MARKUP_TAG_END: { + if (buf == '>') { + tag_end[t] = 0; + t = 0; + if (!cho_tag_close_last_unclosed(tag_end, tags, ta)) { + LOG_DEBUG("cho_tag_close_last_unclosed failed."); + return NULL; + } + memset(tag_end, 0, strlen(tag_end)); + state = state_before_tag; + break; + } + if (buf == '\n') { + if (prev_buf == '\\') { + state_before_backslash = STATE_MARKUP_TAG_END; + state = STATE_BACKSLASH; + t--; + break; + } + cho_log(LOG_ERR, "Newline character inside a tag name is invalid."); + return NULL; + } + if (t == 5) { + cho_log(LOG_ERR, "End tag name is too long."); + return NULL; + } + tag_end[t] = buf; + t++; + break; + } + case STATE_MARKUP_ATTR_NAME: { + if (buf == '=') { + tags[ta]->attrs[at]->name = erealloc(tags[ta]->attrs[at]->name, (atn+1) * sizeof(char)); + tags[ta]->attrs[at]->name[atn] = 0; + atn = 0; + state = STATE_MARKUP_ATTR_VALUE; + break; + } + if (is_whitespace(buf)) { + if (at == 0) { + if (!tags[ta]->attrs[at]->name) { + break; + } else { + tags[ta]->attrs[at]->name = erealloc(tags[ta]->attrs[at]->name, (atn+1) * sizeof(char)); + tags[ta]->attrs[at]->name[atn] = 0; + cho_log(LOG_ERR, "Attribute with name '%s' of tag '%s' has no value.", tags[ta]->attrs[at]->name, tag_start); + return NULL; + } + } + if (tags[ta]->attrs[at-1]->name && tags[ta]->attrs[at-1]->value) { + break; + } + if (!tags[ta]->attrs[at-1]->name && !tags[ta]->attrs[at-1]->value) { + break; + } + tags[ta]->attrs[at]->name = erealloc(tags[ta]->attrs[at]->name, (atn+1) * sizeof(char)); + tags[ta]->attrs[at]->name[atn] = 0; + cho_log(LOG_ERR, "Attribute with name '%s' of tag '%s' has no value.", tags[ta]->attrs[at]->name, tag_start); + return NULL; + } + if (buf == '>') { + if (tags[ta]->attrs[at-1]->value) { + cho_tag_attr_free(tags[ta]->attrs[at]); + tags[ta]->attrs[at] = NULL; + atn = 0; + if (!strcmp(tags[ta]->name, "img")) { + cho_text_free((*lines)[li]->items[ly]->u.text); + (*lines)[li]->items[ly]->is_text = false; + image = cho_image_tag_parse(tags[ta]->attrs); + if (!image) { + LOG_DEBUG("cho_image_tag_parse failed."); + return NULL; + } + (*lines)[li]->items[ly]->u.image = image; + ly++; + (*lines)[li]->items = erealloc((*lines)[li]->items, (ly+1) * sizeof(struct ChoLineItem *)); + (*lines)[li]->items[ly] = cho_line_item_new(); + } else { + tag_style = cho_style_parse(tag_start, tags[ta]->attrs, cho_tag_style_inherit(tags, ta-1), &tags[ta]->style_presence); + if (!tag_style) { + LOG_DEBUG("cho_style_parse failed."); + return NULL; + } + tags[ta]->style = tag_style; + switch (state_before_tag) { + case STATE_LYRICS: + cho_style_free((*lines)[li]->items[ly]->u.text->style); + (*lines)[li]->items[ly]->u.text->style = cho_style_copy(tag_style); + break; + case STATE_CHORD: + cho_style_free((*lines)[li]->text_above[c]->u.chord->style); + (*lines)[li]->text_above[c]->u.chord->style = cho_style_copy(tag_style); + break; + case STATE_ANNOTATION: + cho_style_free((*lines)[li]->text_above[c]->u.annot->style); + (*lines)[li]->text_above[c]->u.annot->style = cho_style_copy(tag_style); + break; + case STATE_DIRECTIVE_VALUE: + directive_has_tag = true; + break; + default: + cho_log(LOG_ERR, "Invalid state_before_tag '%s'.", state_enums[state_before_tag]); + return NULL; + } + } + at = 0; + memset(tag_start, 0, strlen(tag_start)); + state = state_before_tag; + break; + } else { + tags[ta]->attrs[at]->name = erealloc(tags[ta]->attrs[at]->name, (atn+1) * sizeof(char)); + tags[ta]->attrs[at]->name[atn] = 0; + atn = 0; + cho_log(LOG_ERR, "Attribute with name '%s' of tag '%s' has no value.", tags[ta]->attrs[at]->name, tag_start); + return NULL; + } + } + if (buf == '\n') { + if (prev_buf == '\\') { + state_before_backslash = STATE_MARKUP_ATTR_NAME; + state = STATE_BACKSLASH; + atn--; + break; + } + cho_log(LOG_ERR, "Newline character inside an tag attribute name is invalid."); + return NULL; + } + tags[ta]->attrs[at]->name = erealloc(tags[ta]->attrs[at]->name, (atn+1) * sizeof(char)); + tags[ta]->attrs[at]->name[atn] = buf; + atn++; + break; + } + case STATE_MARKUP_ATTR_VALUE: { + if (buf == '\n') { + if (prev_buf == '\\') { + state_before_backslash = STATE_MARKUP_ATTR_VALUE; + state = STATE_BACKSLASH; + atv--; + break; + } + cho_log(LOG_ERR, "Newline character inside an attribute value is invalid."); + return NULL; + } + if (avs == -1) { + if (is_whitespace(buf)) { + cho_log(LOG_ERR, "Whitespace character after equals sign is invalid."); + return NULL; + } + if (buf == '>') { + cho_log(LOG_ERR, "Attribute with name '%s' of tag '%s' has no value.", tags[ta]->attrs[at]->name, tag_start); + return NULL; + } + if (buf == '\'') { + avs = AVS_APOSTROPHE; + } else if (buf == '"') { + avs = AVS_QUOTATION_MARK; + } else { + avs = AVS_UNQUOTED; + tags[ta]->attrs[at]->value = erealloc(tags[ta]->attrs[at]->value, (atv+1) * sizeof(char)); + tags[ta]->attrs[at]->value[atv] = buf; + atv++; + } + break; + } + if (avs == AVS_UNQUOTED && buf == '>') { + tags[ta]->attrs[at]->value = erealloc(tags[ta]->attrs[at]->value, (atv+1) * sizeof(char)); + tags[ta]->attrs[at]->value[atv] = 0; + atv = 0; + at++; + tags[ta]->attrs = erealloc(tags[ta]->attrs, (at+1) * sizeof(struct Attr *)); + tags[ta]->attrs[at] = NULL; + if (!strcmp(tags[ta]->name, "img")) { + cho_text_free((*lines)[li]->items[ly]->u.text); + (*lines)[li]->items[ly]->is_text = false; + image = cho_image_tag_parse(tags[ta]->attrs); + if (!image) { + LOG_DEBUG("cho_image_tag_parse failed."); + return NULL; + } + (*lines)[li]->items[ly]->u.image = image; + ly++; + (*lines)[li]->items = erealloc((*lines)[li]->items, (ly+1) * sizeof(struct ChoLineItem *)); + (*lines)[li]->items[ly] = cho_line_item_new(); + } else { + tag_style = cho_style_parse(tag_start, tags[ta]->attrs, cho_tag_style_inherit(tags, ta-1), &tags[ta]->style_presence); + if (!tag_style) { + LOG_DEBUG("cho_style_parse failed."); + return NULL; + } + tags[ta]->style = tag_style; + switch (state_before_tag) { + case STATE_LYRICS: + cho_style_free((*lines)[li]->items[ly]->u.text->style); + (*lines)[li]->items[ly]->u.text->style = cho_style_copy(tag_style); + break; + case STATE_CHORD: + cho_style_free((*lines)[li]->text_above[c]->u.chord->style); + (*lines)[li]->text_above[c]->u.chord->style = cho_style_copy(tag_style); + break; + case STATE_ANNOTATION: + cho_style_free((*lines)[li]->text_above[c]->u.annot->style); + (*lines)[li]->text_above[c]->u.annot->style = cho_style_copy(tag_style); + break; + case STATE_DIRECTIVE_VALUE: + directive_has_tag = true; + break; + default: + cho_log(LOG_ERR, "Invalid state_before_tag '%s'.", state_enums[state_before_tag]); + return NULL; + } + } + at = 0; + avs = -1; + memset(tag_start, 0, strlen(tag_start)); + state = state_before_tag; + break; + } + if ( + (avs == AVS_APOSTROPHE && buf == '\'') || + (avs == AVS_QUOTATION_MARK && buf == '"') || + (avs == AVS_UNQUOTED && (buf == ' ' || buf == '\t')) + ) { + tags[ta]->attrs[at]->value = erealloc(tags[ta]->attrs[at]->value, (atv+1) * sizeof(char)); + tags[ta]->attrs[at]->value[atv] = 0; + atv = 0; + at++; + tags[ta]->attrs = erealloc(tags[ta]->attrs, (at+1) * sizeof(struct Attr *)); + tags[ta]->attrs[at] = cho_tag_attr_new(); + avs = -1; + state = STATE_MARKUP_ATTR_NAME; + break; + } + tags[ta]->attrs[at]->value = erealloc(tags[ta]->attrs[at]->value, (atv+1) * sizeof(char)); + tags[ta]->attrs[at]->value[atv] = buf; + atv++; + break; + } + case STATE_COMMENT: { + if (buf == '\n') { + state = state_before_comment; + break; + } + break; + } + } + prev_buf = buf; + } else { + break; + } + } + int e = 0; + while (e <= ta) { + cho_tag_free(tags[e]); + e++; + } + free(tags); + if ((*lines)[li]->items[ly]->is_text) { + if ((*lines)[li]->items[ly]->u.text->text) { + (*lines)[li]->items[ly]->u.text->text = erealloc((*lines)[li]->items[ly]->u.text->text, (te+1) * sizeof(char)); + (*lines)[li]->items[ly]->u.text->text[te] = 0; + ly++; + (*lines)[li]->items = erealloc((*lines)[li]->items, (ly+1) * sizeof(struct ChoLineItem *)); + (*lines)[li]->items[ly] = NULL; + (*lines)[li]->text_above = erealloc((*lines)[li]->text_above, (c+1) * sizeof(struct ChoLineItemAbove *)); + (*lines)[li]->text_above[c] = NULL; + li++; + *lines = erealloc(*lines, (li+1) * sizeof(struct ChoLine *)); + (*lines)[li] = NULL; + } else { + cho_line_item_free((*lines)[li]->items[ly]); + free((*lines)[li]->items); + free((*lines)[li]->text_above); + free((*lines)[li]); + (*lines)[li] = NULL; + } + } + if (!cho_style_reset_default()) { + LOG_DEBUG("cho_style_reset_default failed."); + return NULL; + } + songs[so]->metadata = erealloc(songs[so]->metadata, (m+1) * sizeof(struct ChoMetadata *)); + songs[so]->metadata[m] = NULL; + se++; + songs[so]->sections = erealloc(songs[so]->sections, (se+1) * sizeof(struct ChoSection *)); + songs[so]->sections[se] = NULL; + songs[so]->diagrams = erealloc(songs[so]->diagrams, (dia+1) * sizeof(struct ChordDiagram *)); + songs[so]->diagrams[dia] = NULL; + so++; + songs = erealloc(songs, (so+1) * sizeof(struct ChoSong *)); + songs[so] = NULL; + g_current_ttype = TT_TEXT; + g_prev_ttype = TT_TEXT; + g_config = NULL; + g_chordpro_filepath = NULL; + free(g_transpose_history); + g_transpose_history = NULL; + g_transpose = NULL; + g_line_number = 1; + for (e = 0; e<g_ia; e++) { + cho_image_free(g_image_assets[e]); + } + free(g_image_assets); + g_image_assets = NULL; + g_ia = 0; + bool exist_title = false; + for (so = 0; songs[so]; so++) { + for (m = 0; songs[so]->metadata[m]; m++) { + if ( + !strcmp(songs[so]->metadata[m]->name, "title") && + songs[so]->metadata[m]->value && + strcmp(songs[so]->metadata[m]->value, "") != 0 + ) { + exist_title = true; + } + } + if (!exist_title) { + g_line_number = 0; + /* INFO: This cho_log() is not line specific. It's a workaround. */ + cho_log(LOG_ERR, "Song has no title."); + return NULL; + } + exist_title = false; + } + return songs; +} + +#ifdef DEBUG +void +cho_debug_songs_print(struct ChoSong **songs) +{ + struct ChoSong **s; + struct ChoSection **se; + struct ChoLine **li; + struct ChoLineItem **it; + struct ChoLineItemAbove **above; + char *name; + for (s = songs; *s; s++) { + for (se = (*s)->sections; *se; se++) { + printf("## Section"); + if ((*se)->label) { + printf(": %s\n", (*se)->label->text); + } else { + printf("\n"); + } + for (li = (*se)->lines; *li; li++) { + printf("## Line\n"); + it = (*li)->items; + for (above = (*li)->text_above; *above; above++) { + if ((*above)->is_chord) { + name = cho_chord_name_generate((*above)->u.chord); + printf("chord: %s\n", name); + free(name); + } else { + printf("annotation: %s\n", (*above)->u.annot->text); + } + } + for (it = (*li)->items; *it; it++) { + if ((*it)->is_text) { + printf("text: %s\n", (*it)->u.text->text); + } else { + printf("image: %s\n", (*it)->u.image->src); + } + } + } + } + } +} +#endif /* DEBUG */ diff --git a/src/chordpro.h b/src/chordpro.h @@ -0,0 +1,175 @@ +#include <stdint.h> +#include "types.h" + +#ifndef _CHORDPRO_H_ +#define _CHORDPRO_H_ + +#define ERROR -1.0 +#define EMPTY_DOUBLE -1.0 +#define EMPTY_INT -1 +#define DEFAULT_FONT_SIZE 14.0 +#define DEFAULT_TITLE_FONT_SIZE 18.0 +// INFO: Based on https://stackoverflow.com/a/417184 +#define URL_MAX_LEN 2000 +#define FONT_NAME_MAX 100 + +enum AttrValueSyntax : int8_t { + AVS_QUOTATION_MARK, + AVS_APOSTROPHE, + AVS_UNQUOTED +}; + +enum ChordDiagramState { + CDS_NAME, + CDS_OPTION_NAME, + CDS_BASE_FRET, + CDS_FRETS, + CDS_FINGERS, + CDS_KEYS, + CDS_DIAGRAM, + CDS_COPY +}; + +enum ChordDirective { + TRANSPOSE, DEFINE /* , CHORD */ +}; + +enum DirectiveType { + DT_ENVIRONMENT, + DT_METADATA, + DT_FORMATTING, + DT_IMAGE, + DT_PREAMBLE, + DT_FONT, + DT_CHORD, + DT_OUTPUT, + DT_EXTENSION, + DT_CUSTOM +}; + +enum MetadataDirective { + TITLE, + SUBTITLE +}; + +enum OptionState { + OS_NAME, + OS_VALUE +}; + +enum Position { + POS_START, + POS_END, + POS_NO +}; + +enum State { + STATE_LYRICS, + STATE_BACKSLASH, + STATE_DIRECTIVE_NAME, + STATE_DIRECTIVE_VALUE, + STATE_CHORD, + STATE_ANNOTATION, + STATE_TAB, + STATE_MARKUP_TAG, + STATE_MARKUP_TAG_START, + STATE_MARKUP_TAG_END, + STATE_MARKUP_ATTR_NAME, + STATE_MARKUP_ATTR_VALUE, + STATE_COMMENT +}; + +enum StylePropertyType : int8_t { + SPT_FONT, + SPT_SIZE, + SPT_COLOR +}; + +struct Attr { + char *name; + char *value; +}; + +/* + INFO: Depending on the 'dtype' the other + fields have a meaningful value or not. +*/ + +struct ChoDirective { + enum DirectiveType dtype; + enum SectionType stype; + enum Position position; + enum StylePropertyType sprop; + enum TextType ttype; + enum BreakType btype; + enum MetadataDirective meta; + enum ChordDirective ctype; + struct ChoStyle *style; +}; + +union StylePropertyValue { + char *font_name; + double font_size; + struct RGBColor *foreground_color; +}; + +struct StyleProperty { + enum TextType ttype; + enum StylePropertyType type; + union StylePropertyValue u; +}; + +struct Tag { + char *name; + struct ChoStyle *style; + struct ChoStylePresence style_presence; + struct Attr **attrs; + bool is_closed; +}; + +void cho_log_enable_info_logs(void); + +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); +char *cho_chord_name_generate(struct ChoChord *chord); + +void cho_chords_add(struct ChoChord ***chords, struct ChoChord *chord); +bool cho_chords_has(struct ChoChord **chords, struct ChoChord *chord); +size_t cho_chord_count(struct ChoChord **chords); +int cho_chord_compare(const void *a, const void *b); +void cho_chords_free(struct ChoChord **chords); + +const char *cho_metadata_get(struct ChoMetadata **metadata, const char *name); + +struct ChoStyle *cho_style_new(void); +void cho_style_free(struct ChoStyle *style); +struct ChoStyle *cho_style_copy(struct ChoStyle *style); +void cho_style_print_as_toml(struct ChoStyle *style, const char *section); + +struct RGBColor *cho_rgbcolor_new(uint8_t red, uint8_t green, uint8_t blue); +struct RGBColor *cho_color_parse(const char *str); +struct RGBColor *cho_color_copy(struct RGBColor *color); +enum LineStyle cho_linestyle_parse(const char *str); + +// const char *cho_image_name_create(struct ChoImage *image, const char *dirname); + +void cho_font_free(struct Font *font); +void cho_font_print(struct Font *font); +struct Font *cho_font_copy(struct Font *font); +void cho_fonts_free(struct Font **fonts); +char *cho_font_name_normalize(const char *name); +enum FontFamily cho_font_family_parse(const char *str); +const char *cho_font_family_to_config_string(enum FontFamily font_family); +enum FontStyle cho_font_style_parse(const char *str); +const char *cho_font_style_to_config_string(enum FontStyle style); +enum FontWeight cho_font_weight_parse(const char *str); +const char *cho_font_weight_to_config_string(enum FontWeight weight); + +void cho_debug_style_print(struct ChoStyle *style); +// void cho_debug_songs_print(struct ChoSong **songs); + +#endif /* _CHORDPRO_H_ */ diff --git a/src/config.c b/src/config.c @@ -0,0 +1,1065 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <toml.h> +#include "types.h" +#include "config.h" +#include "chordpro.h" +#include "util.h" +#include "chord_diagram.h" + +static const char *notation_systems[] = { + "common", + "german", + "scandinavian", + "latin", + "roman", + "nashville", + "custom" // TODO: Is this needed +}; + +static const char *parse_modes[] = { + "strict", + "relaxed" +}; + +static const char *instruments[] = { + "guitar", + "keyboard", + "mandolin", + "ukulele" +}; + +static const char *text_types[] = { + "chord", "annotation", "chorus", + "footer", "grid", "tab", "toc", "toc_title", "text", + "title", "subtitle", "label", "comment", + "comment_italic", "comment_box" +}; + +static const char *alignments[] = { + "left", "center", "right" +}; + +/* static const char *g_valid_styles[] = { + "title", + "subtitle", + "footer", // TODO + "text", + "chorus", + "chord", + "annotation", + "comment", + "comment_italic", + "comment_boxed", + "tab", + "label", + "toc", + "grid", + "grid_margin", // TODO + "empty", // TODO + "diagram", // TODO + "diagram_base", // TODO + "chordfingers" // TODO +}; */ + +static struct Note notes_common[] = { + { .note = "C", .sharp = "C#", .flat = NULL }, + { .note = "D", .sharp = "D#", .flat = "Db" }, + { .note = "E", .sharp = NULL, .flat = "Eb" }, + { .note = "F", .sharp = "F#", .flat = NULL }, + { .note = "G", .sharp = "G#", .flat = "Gb" }, + { .note = "A", .sharp = "A#", .flat = "Ab" }, + { .note = "B", .sharp = NULL, .flat = "Bb" } +}; + +static struct Note notes_german[] = { + { .note = "C", .sharp = "Cis", .flat = NULL }, + { .note = "D", .sharp = "Dis", .flat = "Des" }, + { .note = "E", .sharp = NULL, .flat = "Es" }, + { .note = "F", .sharp = "Fis", .flat = NULL }, + { .note = "G", .sharp = "Gis", .flat = "Ges" }, + { .note = "A", .sharp = "Ais", .flat = "As" }, + { .note = "H", .sharp = NULL, .flat = "B"}, +}; + +static struct Note notes_scandinavian[] = { + { .note = "C", .sharp = "C#", .flat = NULL }, + { .note = "D", .sharp = "D#", .flat = "Db"}, + { .note = "E", .sharp = NULL, .flat = "Eb"}, + { .note = "F", .sharp = "F#", .flat = NULL }, + { .note = "G", .sharp = "G#", .flat = "Gb"}, + { .note = "A", .sharp = "A#", .flat = "Ab"}, + { .note = "H", .sharp = NULL, .flat = "B"}, +}; + +static struct Note notes_latin[] = { + { .note = "Do", .sharp = "Do#", .flat = NULL }, + { .note = "Re", .sharp = "Re#", .flat = "Reb"}, + { .note = "Mi", .sharp = NULL, .flat = "Mib"}, + { .note = "Fa", .sharp = "Fa#", .flat = NULL }, + { .note = "Sol", .sharp = "Sol#", .flat = "Solb"}, + { .note = "La", .sharp = "La#", .flat = "Lab"}, + { .note = "Si", .sharp = NULL, .flat = "Sib"}, +}; + +static struct Note notes_roman[] = { + { .note = "I", .sharp = "I#", .flat = NULL }, + { .note = "II", .sharp = "II#", .flat = "IIb" }, + { .note = "III", .sharp = NULL, .flat = "IIIb" }, + { .note = "IV", .sharp = "IV#", .flat = NULL }, + { .note = "V", .sharp = "V#", .flat = "Vb" }, + { .note = "VI", .sharp = "VI#", .flat = "VIb" }, + { .note = "VII", .sharp = NULL, .flat = "VIIb" }, +}; + +static struct Note notes_nashville[] = { + { .note = "1", .sharp = "1#", .flat = NULL }, + { .note = "2", .sharp = "2#", .flat = "2b" }, + { .note = "3", .sharp = NULL, .flat = "3b" }, + { .note = "4", .sharp = "4#", .flat = NULL }, + { .note = "5", .sharp = "5#", .flat = "5b" }, + { .note = "6", .sharp = "6#", .flat = "6b" }, + { .note = "7", .sharp = NULL, .flat = "7b" }, +}; + +static enum TextType +config_text_type_parse(const char *str) +{ + if (!strcmp(str, text_types[TT_CHORD])) { + return TT_CHORD; + } else + if (!strcmp(str, text_types[TT_ANNOT])) { + return TT_ANNOT; + } else + if (!strcmp(str, text_types[TT_CHORUS])) { + return TT_CHORUS; + } else + if (!strcmp(str, text_types[TT_FOOTER])) { + return TT_FOOTER; + } else + if (!strcmp(str, text_types[TT_GRID])) { + return TT_GRID; + } else + if (!strcmp(str, text_types[TT_TAB])) { + return TT_TAB; + } else + if (!strcmp(str, text_types[TT_TOC])) { + return TT_TOC; + } else + if (!strcmp(str, text_types[TT_TOC_TITLE])) { + return TT_TOC_TITLE; + } else + if (!strcmp(str, text_types[TT_TEXT])) { + return TT_TEXT; + } else + if (!strcmp(str, text_types[TT_TITLE])) { + return TT_TITLE; + } else + if (!strcmp(str, text_types[TT_SUBTITLE])) { + return TT_SUBTITLE; + } else + if (!strcmp(str, text_types[TT_LABEL])) { + return TT_LABEL; + } else + if (!strcmp(str, text_types[TT_COMMENT])) { + return TT_COMMENT; + } else + if (!strcmp(str, text_types[TT_COMMENT_ITALIC])) { + return TT_COMMENT_ITALIC; + } else + if (!strcmp(str, text_types[TT_COMMENT_BOX])) { + return TT_COMMENT_BOX; + } + return -1; +} + +static enum NotationSystem +config_notation_system_parse(const char *str) +{ + if (!strcmp(str, notation_systems[NS_COMMON]) || !strcmp(str, "dutch")) { + return NS_COMMON; + } else if (!strcmp(str, notation_systems[NS_GERMAN])) { + return NS_GERMAN; + } else if (!strcmp(str, notation_systems[NS_SCANDINAVIAN])) { + return NS_SCANDINAVIAN; + } else if (!strcmp(str, notation_systems[NS_LATIN])) { + return NS_LATIN; + } else if (!strcmp(str, notation_systems[NS_ROMAN])) { + return NS_ROMAN; + } else if (!strcmp(str, notation_systems[NS_NASHVILLE])) { + return NS_NASHVILLE; + } else { + return NS_CUSTOM; + } +} + +static const char * +config_notation_system_to_config_string(enum NotationSystem system) +{ + return notation_systems[system]; +} + +static enum Instrument +config_instrument_parse(const char *str) +{ + if (!strcmp(str, "guitar")) { + return INS_GUITAR; + } else + if (!strcmp(str, "keyboard")) { + return INS_KEYBOARD; + } else + if (!strcmp(str, "mandolin")) { + return INS_MANDOLIN; + } else + if (!strcmp(str, "ukulele")) { + return INS_UKULELE; + } + return -1; +} + +static const char * +config_instrument_to_config_string(enum Instrument ins) +{ + return instruments[ins]; +} + +static struct Note * +config_note_new(void) +{ + struct Note *note = emalloc(sizeof(struct Note)); + note->note = NULL; + note->sharp = NULL; + note->flat = NULL; + return note; +} + +static void +config_note_free(struct Note *note) +{ + free(note->note); + free(note->sharp); + free(note->flat); + free(note); +} + +static void +config_notes_free(struct Note **notes) +{ + int i; + for (i = 0; i<7; i++) { + config_note_free(notes[i]); + } + free(notes); +} + +static struct Note ** +config_notes_new_default(enum NotationSystem system) +{ + struct Note **notes_default = emalloc(8 * sizeof(struct Note *)); + struct Note *notes; + switch (system) { + case NS_GERMAN: + notes = (struct Note *)&notes_german; + break; + case NS_SCANDINAVIAN: + notes = (struct Note *)&notes_scandinavian; + break; + case NS_LATIN: + notes = (struct Note *)&notes_latin; + break; + case NS_ROMAN: + notes = (struct Note *)&notes_roman; + break; + case NS_NASHVILLE: + notes = (struct Note *)&notes_nashville; + break; + default: + notes = (struct Note *)&notes_common; + break; + } + int i; + for (i = 0; i<7; i++) { + notes_default[i] = config_note_new(); + if (notes[i].note) { + notes_default[i]->note = strdup(notes[i].note); + } + if (notes[i].sharp) { + notes_default[i]->sharp = strdup(notes[i].sharp); + } + if (notes[i].flat) { + notes_default[i]->flat = strdup(notes[i].flat); + } + } + // notes_default[7] = NULL; // TODO: This is probably needless + return notes_default; +} + +static struct Note ** +config_notes_load(toml_table_t *notes, const char *system) +{ + struct Note **custom_notes = emalloc(8 * sizeof(struct Note *)); + toml_array_t *arr = toml_table_array(notes, system); + int arr_len = toml_array_len(arr); + if (arr_len != 7) { + util_log(LOG_ERR, "Custom notation system '%s' in [notes] has to have exactly 7 items. For an example see `lorid --print-default-config`.", system); + free(notes); + return NULL; + } + toml_table_t *note; + toml_value_t value; + int i; + for (i = 0; i<arr_len; i++) { + note = toml_array_table(arr, i); + if (note) { + custom_notes[i] = config_note_new(); + value = toml_table_string(note, "note"); + if (value.ok) { + custom_notes[i]->note = value.u.s; + } + value = toml_table_string(note, "sharp"); + if (value.ok) { + custom_notes[i]->sharp = value.u.s; + if (i == 2 || i == 6) { + util_log(LOG_ERR, "Custom notation system '%s' in [notes] can't have sharp value at array index '%d'.", system, i); + goto CLEAN; + } + } + value = toml_table_string(note, "flat"); + if (value.ok) { + custom_notes[i]->flat = value.u.s; + if (i == 0 || i == 3) { + util_log(LOG_ERR, "Custom notation system '%s' in [notes] can't have flat value at array index '%d'.", system, i); + goto CLEAN; + } + } + } + } + custom_notes[7] = NULL; + return custom_notes; + CLEAN: + for (int k=i; k>=0; k--) { + config_note_free(custom_notes[k]); + } + free(custom_notes); + return NULL; +} + +static void +config_notes_print_as_toml(enum NotationSystem system) +{ + struct Note *notes; + switch (system) { + case NS_COMMON: + notes = (struct Note *)&notes_common; + break; + case NS_GERMAN: + notes = (struct Note *)&notes_german; + break; + case NS_SCANDINAVIAN: + notes = (struct Note *)&notes_scandinavian; + break; + case NS_LATIN: + notes = (struct Note *)&notes_latin; + break; + case NS_ROMAN: + notes = (struct Note *)&notes_roman; + break; + case NS_NASHVILLE: + notes = (struct Note *)&notes_nashville; + break; + case NS_CUSTOM: + return; + } + printf("%s = [\n", config_notation_system_to_config_string(system)); + int i; + for (i = 0; i<7; i++) { + printf("\t{ note = \"%s\",", notes[i].note); + if (notes[i].sharp) { + printf(" sharp = \"%s\",", notes[i].sharp); + } + if (notes[i].flat) { + printf(" flat = \"%s\"", notes[i].flat); + } + printf(" },\n"); + } + printf("]\n\n"); +} + +static const char * +config_parse_mode_to_config_string(enum ParseMode mode) +{ + return parse_modes[mode]; +} + +static enum Alignment +config_alignment_parse(const char *str) +{ + if (!strcmp(str, "left")) { + return A_LEFT; + } else + if (!strcmp(str, "center")) { + return A_CENTER; + } else + if (!strcmp(str, "right")) { + return A_RIGHT; + } + return -1; +} + +static const char * +config_alignment_to_config_string(enum Alignment align) +{ + return alignments[align]; +} + +static struct Config * +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; + config->output->diagram = emalloc(sizeof(struct ChordDiagram)); + config->output->diagram->show = true; + config->output->diagram->instrument = INS_GUITAR; + config->output->page_no = emalloc(sizeof(struct ConfigPageNo)); + config->output->page_no->show = true; + config->output->page_no->align = A_CENTER; + config->output->notation_system = NS_COMMON; + config->output->start_song_on_new_page = true; + config->output->styles = emalloc(TT_LENGTH * sizeof(struct ChoStyle *)); + + config->output->styles[TT_CHORD] = cho_style_new(); + config->output->styles[TT_CHORD]->font->name = strdup(DEFAULT_FONT_FAMILY); + config->output->styles[TT_CHORD]->font->weight = FW_BOLD; + config->output->styles[TT_ANNOT] = cho_style_new(); + config->output->styles[TT_ANNOT]->font->name = strdup(DEFAULT_FONT_FAMILY); + config->output->styles[TT_ANNOT]->font->style = FS_ITALIC; + config->output->styles[TT_CHORUS] = cho_style_new(); + config->output->styles[TT_CHORUS]->font->name = strdup(DEFAULT_FONT_FAMILY); + config->output->styles[TT_FOOTER] = cho_style_new(); + config->output->styles[TT_GRID] = cho_style_new(); + config->output->styles[TT_GRID]->font->name = strdup(DEFAULT_FONT_FAMILY); + config->output->styles[TT_GRID]->font->weight = FW_BOLD; + config->output->styles[TT_TAB] = cho_style_new(); + config->output->styles[TT_TAB]->font->name = strdup("Courier"); + // config->output->styles[TT_TAB]->font->family = FF_MONOSPACE; + config->output->styles[TT_TOC] = cho_style_new(); + config->output->styles[TT_TOC]->font->name = strdup(DEFAULT_FONT_FAMILY); + config->output->styles[TT_TOC]->font->size = 12.0; + config->output->styles[TT_TOC_TITLE] = cho_style_new(); + config->output->styles[TT_TOC_TITLE]->font->name = strdup(DEFAULT_FONT_FAMILY); + config->output->styles[TT_TOC_TITLE]->font->weight = FW_BOLD; + config->output->styles[TT_TOC_TITLE]->font->size = 18.0; + config->output->styles[TT_TEXT] = cho_style_new(); + config->output->styles[TT_TEXT]->font->name = strdup(DEFAULT_FONT_FAMILY); + config->output->styles[TT_TITLE] = cho_style_new(); + config->output->styles[TT_TITLE]->font->name = strdup(DEFAULT_FONT_FAMILY); + config->output->styles[TT_TITLE]->font->weight = FW_BOLD; + config->output->styles[TT_TITLE]->font->size = 18.0; + config->output->styles[TT_SUBTITLE] = cho_style_new(); + config->output->styles[TT_SUBTITLE]->font->name = strdup(DEFAULT_FONT_FAMILY); + config->output->styles[TT_SUBTITLE]->font->size = 12.0; + config->output->styles[TT_LABEL] = cho_style_new(); + config->output->styles[TT_LABEL]->font->name = strdup(DEFAULT_FONT_FAMILY); + config->output->styles[TT_LABEL]->font->style = FS_ITALIC; + config->output->styles[TT_COMMENT] = cho_style_new(); + config->output->styles[TT_COMMENT]->font->name = strdup(DEFAULT_FONT_FAMILY); + config->output->styles[TT_COMMENT]->background_color->red = 228; + config->output->styles[TT_COMMENT]->background_color->green = 228; + config->output->styles[TT_COMMENT]->background_color->blue = 228; + config->output->styles[TT_COMMENT_ITALIC] = cho_style_new(); + config->output->styles[TT_COMMENT_ITALIC]->font->name = strdup(DEFAULT_FONT_FAMILY); + config->output->styles[TT_COMMENT_ITALIC]->font->style = FS_ITALIC; + config->output->styles[TT_COMMENT_BOX] = cho_style_new(); + config->output->styles[TT_COMMENT_BOX]->font->name = strdup(DEFAULT_FONT_FAMILY); + config->output->styles[TT_COMMENT_BOX]->boxed = true; + + // config->output->styles[15] = NULL; + config->output->notes = config_notes_new_default(NS_COMMON); + config->parser = emalloc(sizeof(struct ConfigParser)); + config->parser->chords = emalloc(sizeof(struct ConfigChords)); + config->parser->chords->notation_system = NS_COMMON; + config->parser->chords->mode = PM_STRICT; + config->parser->notes = config_notes_new_default(NS_COMMON); + return config; +} + +void +config_print_default(void) +{ + struct Config *config = config_load_default(); + printf("[notes]\n"); + config_notes_print_as_toml(NS_COMMON); + config_notes_print_as_toml(NS_GERMAN); + config_notes_print_as_toml(NS_SCANDINAVIAN); + config_notes_print_as_toml(NS_LATIN); + config_notes_print_as_toml(NS_ROMAN); + config_notes_print_as_toml(NS_NASHVILLE); + + printf("[parser]\n"); + printf("[parser.chords]\n"); + printf("mode = \"%s\"\n", config_parse_mode_to_config_string(config->parser->chords->mode)); + printf("notation_system = \"%s\"\n\n", config_notation_system_to_config_string(config->parser->chords->notation_system)); + + printf("[output]\n"); + printf("notation_system = \"%s\"\n", config_notation_system_to_config_string(config->output->notation_system)); + printf("start_song_on_new_page = %s\n\n", config->output->start_song_on_new_page ? "true" : "false"); + printf("[output.toc]\n"); + printf("show = %s\n", config->output->toc->show ? "true" : "false"); + printf("title = \"%s\"\n\n", config->output->toc->title); + printf("[output.chord_diagram]\n"); + printf("show = %s\n", config->output->diagram->show ? "true" : "false"); + printf("instrument = \"%s\"\n\n", config_instrument_to_config_string(config->output->diagram->instrument)); + printf("[output.chorus]\n"); + printf("label = \"Chorus\"\n"); + printf("quote = false\n\n"); + printf("[output.page_no]\n"); + printf("show = %s\n", config->output->page_no->show ? "true" : "false"); + printf("alignment = \"%s\"\n\n", config_alignment_to_config_string(config->output->page_no->align)); + printf("[output.styles]\n"); + int i; + for (i = 1; i<TT_LENGTH; i++) { + printf("[output.styles.%s]\n\n", text_types[i]); + cho_style_print_as_toml(config->output->styles[i], text_types[i]); + } + config_free(config); +} + +static bool +config_load_font(struct Font *font, toml_table_t *table, const char *key_name, struct ChoStylePresence *presence) +{ + enum FontFamily family; + enum FontStyle style; + enum FontWeight weight; + toml_value_t value; + value = toml_table_string(table, "name"); + if (value.ok) { + presence->font.name = true; + free(font->name); + font->name = value.u.s; + } + value = toml_table_string(table, "family"); + if (value.ok) { + presence->font.family = true; + family = cho_font_family_parse(value.u.s); + if (family != -1) { + font->family = family; + } else { + util_log(LOG_ERR, "Config section [output.styles.%s.font] family value is invalid.", key_name); + return false; + } + free(value.u.s); + } + value = toml_table_string(table, "style"); + if (value.ok) { + presence->font.style = true; + style = cho_font_style_parse(value.u.s); + if (style != -1) { + font->style = style; + } else { + util_log(LOG_ERR, "Config section [output.styles.%s.font] style value is invalid.", key_name); + return false; + } + free(value.u.s); + } + value = toml_table_string(table, "weight"); + if (value.ok) { + presence->font.weight = true; + weight = cho_font_weight_parse(value.u.s); + if (weight != -1) { + font->weight = weight; + } else { + util_log(LOG_ERR, "Config section [output.styles.%s.font] weight value is invalid.", key_name); + return false; + } + free(value.u.s); + } + value = toml_table_int(table, "size"); + if (value.ok) { + presence->font.size = true; + font->size = value.u.i; + } + return true; +} + +static bool +config_load_style( + struct ChoStyle *style, + toml_table_t *table, + const char *key_name, + struct ChoStylePresence *presence +) +{ + toml_value_t value; + struct RGBColor *color; + enum LineStyle line_style; + toml_table_t *font_section = toml_table_table(table, "font"); + if (font_section) { + if (!config_load_font(style->font, font_section, key_name, presence)) { + LOG_DEBUG("config_load_font failed."); + return false; + } + } + value = toml_table_string(table, "foreground_color"); + if (value.ok) { + presence->foreground_color = true; + color = cho_color_parse(value.u.s); + if (color) { + free(style->foreground_color); + style->foreground_color = color; + } else { + util_log(LOG_ERR, "Config section [output.styles.%s] foreground color value is invalid.", key_name); + return false; + } + free(value.u.s); + } + value = toml_table_string(table, "background_color"); + if (value.ok) { + presence->background_color = true; + color = cho_color_parse(value.u.s); + if (color) { + free(style->background_color); + style->background_color = color; + } else { + util_log(LOG_ERR, "Config section [output.styles.%s] background color value is invalid.", key_name); + return false; + } + free(value.u.s); + } + value = toml_table_string(table, "underline_style"); + if (value.ok) { + presence->underline_style = true; + line_style = cho_linestyle_parse(value.u.s); + if (line_style != -1) { + style->underline_style = line_style; + } else { + util_log(LOG_ERR, "Config section [output.styles.%s] underline style value is invalid.", key_name); + return false; + } + free(value.u.s); + } + value = toml_table_string(table, "underline_color"); + if (value.ok) { + presence->underline_color = true; + color = cho_color_parse(value.u.s); + if (color) { + free(style->underline_color); + style->underline_color = color; + } else { + util_log(LOG_ERR, "Config section [output.styles.%s] underline color value is invalid.", key_name); + return false; + } + free(value.u.s); + } + value = toml_table_string(table, "overline_style"); + if (value.ok) { + presence->overline_style = true; + line_style = cho_linestyle_parse(value.u.s); + if (line_style != -1) { + style->overline_style = line_style; + } else { + util_log(LOG_ERR, "Config section [output.styles.%s] overline style value is invalid.", key_name); + return false; + } + free(value.u.s); + } + value = toml_table_string(table, "overline_color"); + if (value.ok) { + presence->overline_color = true; + color = cho_color_parse(value.u.s); + if (color) { + free(style->overline_color); + style->overline_color = color; + } else { + util_log(LOG_ERR, "Config section [output.styles.%s] overline color valeu is invalid.", key_name); + return false; + } + free(value.u.s); + } + value = toml_table_bool(table, "strikethrough"); + if (value.ok) { + presence->strikethrough = true; + style->strikethrough = value.u.b; + } + value = toml_table_string(table, "strikethrough_color"); + if (value.ok) { + presence->strikethrough_color = true; + color = cho_color_parse(value.u.s); + if (color) { + free(style->strikethrough_color); + style->strikethrough_color = color; + } else { + util_log(LOG_ERR, "Config section [output.styles.%s] strikethrough color value is invalid.", key_name); + return false; + } + free(value.u.s); + } + value = toml_table_bool(table, "boxed"); + if (value.ok) { + presence->boxed = true; + style->boxed = value.u.b; + } + value = toml_table_string(table, "boxed_color"); + if (value.ok) { + presence->boxed_color = true; + color = cho_color_parse(value.u.s); + if (color) { + free(style->boxed_color); + style->boxed_color = color; + } else { + util_log(LOG_ERR, "Config section [output.styles.%s] boxed color value is invalid.", key_name); + return false; + } + free(value.u.s); + } + value = toml_table_double(table, "rise"); + if (value.ok) { + presence->rise = true; + style->rise = value.u.d; + } + value = toml_table_string(table, "href"); + if (value.ok) { + presence->href = true; + style->href = value.u.s; + } + return true; +} + +#ifdef DEBUG + +static void +presence_print(const char *name, struct ChoStylePresence *presence) +{ + printf("---- BEGIN PRESENCE ----\n"); + printf("style '%s'\n", name); + printf("font.name %d\n", presence->font.name); + printf("font.family %d\n", presence->font.family); + printf("font.style %d\n", presence->font.style); + printf("font.weight %d\n", presence->font.weight); + printf("font.size %d\n", presence->font.size); + printf("foreground_color %d\n", presence->foreground_color); + printf("background_color %d\n", presence->background_color); + printf("underline_style %d\n", presence->underline_style); + printf("underline_color %d\n", presence->underline_color); + printf("overline_style %d\n", presence->overline_style); + printf("overline_color %d\n", presence->overline_color); + printf("strikethrough %d\n", presence->strikethrough); + printf("strikethrough_color %d\n", presence->strikethrough_color); + printf("boxed %d\n", presence->boxed); + printf("boxed_color %d\n", presence->boxed_color); + printf("rise %d\n", presence->rise); + printf("href %d\n", presence->href); + printf("---- END PRESENCE ------\n"); +} + +#endif /* DEBUG */ + +static void +set_text_style( + struct ChoStylePresence *text_presence, + struct ChoStyle *text_style, + struct ChoStylePresence *presence, + struct ChoStyle *style +) +{ + + if (!presence->font.name && + text_presence->font.name) { + free(style->font->name); + style->font->name = strdup(text_style->font->name); + } + if (!presence->font.family && text_presence->font.family) { + style->font->family = text_style->font->family; + } + if (!presence->font.style && text_presence->font.style) { + style->font->style = text_style->font->style; + } + if (!presence->font.weight && text_presence->font.weight) { + style->font->weight = text_style->font->weight; + } + if (!presence->font.size && text_presence->font.size) { + style->font->size = text_style->font->size; + } + if (!presence->foreground_color && text_presence->foreground_color) { + free(style->foreground_color); + style->foreground_color = cho_color_copy(text_style->foreground_color); + } + if (!presence->background_color && text_presence->background_color) { + free(style->background_color); + style->background_color = cho_color_copy(text_style->background_color); + } + if (!presence->underline_style && text_presence->underline_style) { + style->underline_style = text_style->underline_style; + } + if (!presence->underline_color && text_presence->underline_color) { + free(style->underline_color); + style->underline_color = cho_color_copy(text_style->underline_color); + } + if (!presence->overline_style && text_presence->overline_style) { + style->overline_style = text_style->overline_style; + } + if (!presence->overline_color && text_presence->overline_color) { + free(style->overline_color); + style->overline_color = cho_color_copy(text_style->overline_color); + } + if (!presence->strikethrough && text_presence->strikethrough) { + style->strikethrough = text_style->strikethrough; + } + if (!presence->strikethrough_color && text_presence->strikethrough_color) { + free(style->strikethrough_color); + style->strikethrough_color = cho_color_copy(text_style->strikethrough_color); + } + if (!presence->boxed && text_presence->boxed) { + style->boxed = text_style->boxed; + } + if (!presence->boxed_color && text_presence->boxed_color) { + free(style->boxed_color); + style->boxed_color = cho_color_copy(text_style->boxed_color); + } + if (!presence->rise && text_presence->rise) { + style->rise = text_style->rise; + } + if (!presence->href && text_presence->href) { + free(style->href); + style->href = strdup(text_style->href); + } +} + +static void +lyrics_set_text_style_as_default( + struct ChoStylePresence presences[], + struct ChoStyle **styles +) +{ + struct ChoStyle *style, *text_style; + struct ChoStylePresence *presence, *text_presence; + enum TextType lyric_types[] = { + TT_CHORUS, TT_COMMENT, + TT_COMMENT_ITALIC, TT_COMMENT_BOX + }; + text_presence = &presences[TT_TEXT]; + text_style = styles[TT_TEXT]; + size_t i; + for (i = 0; i<LENGTH(lyric_types); i++) { + presence = &presences[lyric_types[i]]; + style = styles[lyric_types[i]]; + set_text_style(text_presence, text_style, presence, style); + } +} + +struct Config * +config_load(const char *filepath) +{ + struct Config *config = config_load_default(); + char *home = getenv("HOME"); + char path[26+strlen(home)+1]; + if (!filepath) { + sprintf(path, "%s/.config/lorid/config.toml", home); + filepath = path; + } + FILE *fp = fopen(filepath, "r"); + if (!fp) { + util_log(LOG_INFO, "Couldn't open config file '%s'. Using default configuration.", filepath); + return config; + } + char errbuf[200]; + toml_table_t *table = toml_parse_file(fp, (char *)&errbuf, sizeof(errbuf)); + if (!table) { + LOG_DEBUG("toml_parse_file failed."); + util_log(LOG_ERR, "Config file is not a valid toml file: %s.", (char *)&errbuf); + return NULL; + } + toml_table_t *output = toml_table_table(table, "output"); + if (output) { + toml_table_t *styles, *notes, *chorus, *diagram, *toc, *page_no; + toml_value_t value; + enum NotationSystem notation_system; + enum Instrument instrument; + enum Alignment align; + struct Note **custom_notes; + chorus = toml_table_table(output, "chorus"); + if (chorus) { + value = toml_table_string(chorus, "label"); + if (value.ok) { + free(config->output->chorus->label); + config->output->chorus->label = value.u.s; + } + value = toml_table_bool(chorus, "quote"); + if (value.ok) { + 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"); + if (value.ok) { + config->output->diagram->show = value.u.b; + } + value = toml_table_string(diagram, "instrument"); + if (value.ok) { + instrument = config_instrument_parse(value.u.s); + if (instrument == -1) { + util_log(LOG_ERR, "Unknown instrument '%s' in [output.chord_diagram].", value.u.s); + return NULL; + } + config->output->diagram->instrument = instrument; + free(value.u.s); + } + } + value = toml_table_string(output, "notation_system"); + if (value.ok) { + notation_system = config_notation_system_parse(value.u.s); + if (notation_system == NS_CUSTOM) { + notes = toml_table_table(table, "notes"); + if (!notes) { + util_log(LOG_ERR, "Custom notes '%s' has no corresponding definition in [notes].", value.u.s); + return NULL; + } + custom_notes = config_notes_load(notes, value.u.s); + if (custom_notes) { + config_notes_free(config->output->notes); + config->output->notes = custom_notes; + } else { + LOG_DEBUG("config_notes_load failed."); + util_log(LOG_ERR, "Couldn't load custom notes '%s' from [notes] section.", value.u.s); + return NULL; + } + } else { + config_notes_free(config->output->notes); + config->output->notes = config_notes_new_default(notation_system); + } + free(value.u.s); + } + value = toml_table_bool(output, "start_song_on_new_page"); + if (value.ok) { + config->output->start_song_on_new_page = value.u.b; + } + page_no = toml_table_table(output, "page_no"); + if (page_no) { + value = toml_table_bool(page_no, "show"); + if (value.ok) { + config->output->page_no->show = value.u.b; + } + value = toml_table_string(page_no, "alignment"); + if (value.ok) { + align = config_alignment_parse(value.u.s); + if (align == -1) { + LOG_DEBUG("config_alignment_parse failed."); + return NULL; + } + config->output->page_no->align = align; + free(value.u.s); + } + } + styles = toml_table_table(output, "styles"); + if (styles) { + int i, unused; + const char *key_name; + enum TextType ttype; + struct ChoStyle *style; + struct ChoStylePresence presences[TT_LENGTH] = {0}; + toml_table_t *key; + for (i = 0; i<toml_table_len(styles); i++) { + key_name = toml_table_key(styles, i, &unused); + ttype = config_text_type_parse(key_name); + if (ttype != -1) { + key = toml_table_table(styles, key_name); + if (key) { + style = config->output->styles[ttype]; + if (!config_load_style(style, key, key_name, &presences[ttype])) { + LOG_DEBUG("config_load_style failed."); + return NULL; + } + } + } + } + lyrics_set_text_style_as_default(presences, config->output->styles); + } + } + toml_table_t *parser = toml_table_table(table, "parser"); + if (parser) { + toml_table_t *chords = toml_table_table(parser, "chords"); + if (chords) { + toml_table_t *notes; + toml_value_t value; + enum NotationSystem notation_system; + struct Note **custom_notes; + value = toml_table_string(chords, "notation_system"); + if (value.ok) { + notation_system = config_notation_system_parse(value.u.s); + if (notation_system == NS_CUSTOM) { + notes = toml_table_table(table, "notes"); + if (!notes) { + util_log(LOG_ERR, "Custom notes '%s' has no corresponding definition in [notes].", value.u.s); + return NULL; + } + custom_notes = config_notes_load(notes, value.u.s); + if (custom_notes) { + config_notes_free(config->parser->notes); + config->parser->notes = custom_notes; + } else { + LOG_DEBUG("config_notes_load failed."); + util_log(LOG_ERR, "Couldn't load custom notes '%s' from [notes] section.", value.u.s); + return NULL; + } + } else { + config_notes_free(config->parser->notes); + config->parser->notes = config_notes_new_default(notation_system); + } + free(value.u.s); + } + value = toml_table_string(chords, "mode"); + if (value.ok) { + if (!strcmp(value.u.s, "strict")) { + config->parser->chords->mode = PM_STRICT; + } else if (!strcmp(value.u.s, "relaxed")) { + config->parser->chords->mode = PM_RELAXED; + } + free(value.u.s); + } + } + } + toml_free(table); + fclose(fp); + return config; +} + +void +config_free(struct Config *config) +{ + free(config->output->toc->title); + free(config->output->toc); + free(config->output->chorus->label); + free(config->output->chorus); + int i; + for (i = 0; i<TT_LENGTH; i++) { + cho_style_free(config->output->styles[i]); + } + free(config->output->styles); + config_notes_free(config->output->notes); + free(config->output->diagram); + free(config->output->page_no); + free(config->output); + free(config->parser->chords); + config_notes_free(config->parser->notes); + free(config->parser); + free(config); +} diff --git a/src/config.h b/src/config.h @@ -0,0 +1,17 @@ +#include "types.h" + +#ifndef _CONFIG_H_ +#define _CONFIG_H_ + +#ifdef DEBUG +#define SYMBOLS_FILEPATH "./misc/ChordProSymbols.ttf" +#else +#define SYMBOLS_FILEPATH PREFIX"/share/lorid/ChordProSymbols.ttf" +#endif /* DEBUG */ +#define DEFAULT_FONT_FAMILY "Open Sans" + +struct Config *config_load(const char *filepath); +void config_free(struct Config *config); +void config_print_default(void); + +#endif /* _CONFIG_H_ */ diff --git a/src/diagrams.h b/src/diagrams.h @@ -0,0 +1,4394 @@ +static struct StringDiagram guitar_diagrams[] = { + { + .name = "C", + .base_fret = 1, + .frets = { 0, 3, 2, 0, 1, 0, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 3, 2, 0, 1, 0, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Cm", + .base_fret = 3, + .frets = { 1, 1, 3, 3, 2, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 3, 4, 2, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C+", + .base_fret = 1, + .frets = { -1, -1, 2, 1, 1, 4, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 2, 1, 1, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Caug", + .base_fret = 1, + .frets = { -1, -1, 2, 1, 1, 4, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 2, 1, 1, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Cdim", + .base_fret = 3, + .frets = { -1, 1, 2, 3, 2, -1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 1, 2, 4, 3, 0, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C0", + .base_fret = 1, + .frets = { -1, -1, 1, 2, 1, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 3, 2, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Cdim", + .base_fret = 1, + .frets = { -1, -1, 1, 2, 1, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 3, 2, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Cdim7", + .base_fret = 1, + .frets = { -1, -1, 1, 2, 1, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 3, 2, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C7", + .base_fret = 1, + .frets = { 0, 3, 2, 3, 1, 0, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 3, 2, 4, 1, 0, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Cmaj7", + .base_fret = 1, + .frets = { -1, 3, 2, 0, 0, 0, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 3, 2, 0, 0, 0, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Cm7", + .base_fret = 3, + .frets = { 1, 1, 3, 1, 2, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 3, 1, 2, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C#", + .base_fret = 1, + .frets = { -1, -1, 3, 1, 2, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 3, 1, 2, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C#m", + .base_fret = 1, + .frets = { -1, -1, 2, 1, 2, 0, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 2, 1, 3, 0, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C#+", + .base_fret = 2, + .frets = { -1, 3, 2, 1, 1, -1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 4, 3, 1, 2, 0, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C#aug", + .base_fret = 2, + .frets = { -1, 3, 2, 1, 1, -1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 4, 3, 1, 2, 0, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C#dim", + .base_fret = 1, + .frets = { -1, -1, 2, 0, 2, 0, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 3, 0, 4, 0, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C#0", + .base_fret = 2, + .frets = { -1, -1, 1, 2, 1, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 3, 2, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C#dim", + .base_fret = 2, + .frets = { -1, -1, 1, 2, 1, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 3, 2, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C#dim7", + .base_fret = 2, + .frets = { -1, -1, 1, 2, 1, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 3, 2, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C#7", + .base_fret = 2, + .frets = { -1, -1, 2, 3, 1, 3, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 2, 3, 1, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C#maj7", + .base_fret = 1, + .frets = { -1, 4, 3, 1, 1, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 4, 3, 1, 1, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C#m7", + .base_fret = 1, + .frets = { -1, 4, 2, 1, 0, 0, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 4, 2, 1, 0, 0, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Db", + .base_fret = 1, + .frets = { -1, -1, 3, 1, 2, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 3, 1, 2, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Dbm", + .base_fret = 1, + .frets = { -1, -1, 2, 1, 2, 0, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 2, 1, 3, 0, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Db+", + .base_fret = 2, + .frets = { -1, 3, 2, 1, 1, -1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 4, 3, 1, 2, 0, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Dbaug", + .base_fret = 2, + .frets = { -1, 3, 2, 1, 1, -1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 4, 3, 1, 2, 0, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Dbdim", + .base_fret = 1, + .frets = { -1, -1, 2, 0, 2, 0, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 3, 0, 4, 0, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Db0", + .base_fret = 2, + .frets = { -1, -1, 1, 2, 1, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 3, 2, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Dbdim", + .base_fret = 2, + .frets = { -1, -1, 1, 2, 1, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 3, 2, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Dbdim7", + .base_fret = 2, + .frets = { -1, -1, 1, 2, 1, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 3, 2, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Db7", + .base_fret = 2, + .frets = { -1, -1, 2, 3, 1, 3, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 2, 3, 1, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Dbmaj7", + .base_fret = 1, + .frets = { -1, 4, 3, 1, 1, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 4, 3, 1, 1, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Dbm7", + .base_fret = 1, + .frets = { -1, 4, 2, 1, 0, 0, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 4, 2, 1, 0, 0, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D", + .base_fret = 1, + .frets = { -1, -1, 0, 2, 3, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 0, 1, 3, 2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Dm", + .base_fret = 1, + .frets = { -1, -1, 0, 2, 3, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 0, 2, 3, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D+", + .base_fret = 1, + .frets = { -1, -1, 0, 3, 3, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 0, 2, 3, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Daug", + .base_fret = 1, + .frets = { -1, -1, 0, 3, 3, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 0, 2, 3, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ddim", + .base_fret = 1, + .frets = { -1, -1, 0, 1, 3, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 0, 1, 3, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D0", + .base_fret = 1, + .frets = { -1, -1, 0, 1, 0, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 0, 1, 0, 2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ddim", + .base_fret = 1, + .frets = { -1, -1, 0, 1, 0, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 0, 1, 0, 2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ddim7", + .base_fret = 1, + .frets = { -1, -1, 0, 1, 0, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 0, 1, 0, 2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D7", + .base_fret = 1, + .frets = { -1, -1, 0, 2, 1, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 0, 2, 1, 3, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Dmaj7", + .base_fret = 1, + .frets = { -1, -1, 0, 2, 2, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 0, 1, 2, 3, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Dm7", + .base_fret = 1, + .frets = { -1, -1, 0, 2, 1, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 0, 2, 1, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D#", + .base_fret = 3, + .frets = { -1, -1, 3, 1, 2, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 3, 1, 2, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D#m", + .base_fret = 1, + .frets = { -1, -1, 4, 3, 4, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 3, 2, 4, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D#+", + .base_fret = 1, + .frets = { 3, 2, 1, 0, 0, 3, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 2, 1, 0, 0, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D#aug", + .base_fret = 1, + .frets = { 3, 2, 1, 0, 0, 3, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 2, 1, 0, 0, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D#dim", + .base_fret = 2, + .frets = { -1, -1, 3, 1, 3, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 3, 1, 4, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D#0", + .base_fret = 1, + .frets = { -1, -1, 1, 2, 1, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 3, 2, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D#dim", + .base_fret = 1, + .frets = { -1, -1, 1, 2, 1, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 3, 2, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D#dim7", + .base_fret = 1, + .frets = { -1, -1, 1, 2, 1, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 3, 2, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D#7", + .base_fret = 1, + .frets = { -1, -1, 1, 3, 2, 3, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 3, 2, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D#maj7", + .base_fret = 1, + .frets = { -1, -1, 1, 3, 3, 3, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 2, 3, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D#m7", + .base_fret = 1, + .frets = { -1, -1, 1, 3, 2, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 2, 3, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Eb", + .base_fret = 3, + .frets = { -1, -1, 3, 1, 2, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 3, 1, 2, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ebm", + .base_fret = 1, + .frets = { -1, -1, 4, 3, 4, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 3, 2, 4, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Eb+", + .base_fret = 1, + .frets = { 3, 2, 1, 0, 0, 3, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 2, 1, 0, 0, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ebaug", + .base_fret = 1, + .frets = { 3, 2, 1, 0, 0, 3, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 2, 1, 0, 0, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ebdim", + .base_fret = 2, + .frets = { -1, -1, 3, 1, 3, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 3, 1, 4, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Eb0", + .base_fret = 1, + .frets = { -1, -1, 1, 2, 1, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 3, 2, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ebdim", + .base_fret = 1, + .frets = { -1, -1, 1, 2, 1, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 3, 2, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ebdim7", + .base_fret = 1, + .frets = { -1, -1, 1, 2, 1, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 3, 2, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Eb7", + .base_fret = 1, + .frets = { -1, -1, 1, 3, 2, 3, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 3, 2, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ebmaj7", + .base_fret = 1, + .frets = { -1, -1, 1, 3, 3, 3, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 2, 3, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ebm7", + .base_fret = 1, + .frets = { -1, -1, 1, 3, 2, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 2, 3, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "E", + .base_fret = 1, + .frets = { 0, 2, 2, 1, 0, 0, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 2, 3, 1, 0, 0, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Em", + .base_fret = 1, + .frets = { 0, 2, 2, 0, 0, 0, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 2, 3, 0, 0, 0, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "E+", + .base_fret = 1, + .frets = { 0, 3, 2, 1, -1, -1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 3, 2, 1, 0, 0, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Eaug", + .base_fret = 1, + .frets = { 0, 3, 2, 1, -1, -1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 3, 2, 1, 0, 0, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Edim", + .base_fret = 3, + .frets = { -1, -1, 3, 1, 3, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 3, 1, 4, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "E0", + .base_fret = 2, + .frets = { -1, -1, 1, 2, 1, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 3, 2, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Edim", + .base_fret = 2, + .frets = { -1, -1, 1, 2, 1, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 3, 2, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Edim7", + .base_fret = 2, + .frets = { -1, -1, 1, 2, 1, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 3, 2, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "E7", + .base_fret = 1, + .frets = { 0, 2, 0, 1, 0, 0, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 2, 0, 1, 0, 0, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Emaj7", + .base_fret = 1, + .frets = { 0, 2, 1, 1, 0, -1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 3, 1, 2, 0, 0, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Em7", + .base_fret = 1, + .frets = { 0, 2, 0, 0, 0, 0, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 2, 0, 0, 0, 0, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F", + .base_fret = 1, + .frets = { 1, 3, 3, 2, 1, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 4, 2, 1, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Fm", + .base_fret = 1, + .frets = { 1, 3, 3, 1, 1, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 4, 1, 1, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F+", + .base_fret = 1, + .frets = { -1, -1, 1, 4, 4, 3, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 3, 4, 2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Faug", + .base_fret = 1, + .frets = { -1, -1, 1, 4, 4, 3, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 3, 4, 2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Fdim", + .base_fret = 4, + .frets = { -1, -1, 3, 1, 3, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 3, 1, 4, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F0", + .base_fret = 1, + .frets = { -1, -1, 0, 1, 0, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 0, 1, 0, 2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Fdim", + .base_fret = 1, + .frets = { -1, -1, 0, 1, 0, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 0, 1, 0, 2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Fdim7", + .base_fret = 1, + .frets = { -1, -1, 0, 1, 0, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 0, 1, 0, 2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F7", + .base_fret = 1, + .frets = { 1, 3, 1, 2, 1, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 1, 2, 1, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Fmaj7", + .base_fret = 1, + .frets = { -1, -1, 3, 2, 1, 0, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 3, 2, 1, 0, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Fm7", + .base_fret = 1, + .frets = { 1, 3, 1, 1, 1, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 1, 1, 1, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F#", + .base_fret = 2, + .frets = { 1, 3, 3, 2, 1, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 4, 2, 1, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F#m", + .base_fret = 2, + .frets = { 1, 3, 3, 1, 1, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 4, 1, 1, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F#+", + .base_fret = 1, + .frets = { 2, 1, 0, 3, 3, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 0, 4, 4, 3, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F#aug", + .base_fret = 1, + .frets = { 2, 1, 0, 3, 3, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 0, 4, 4, 3, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F#dim", + .base_fret = 5, + .frets = { -1, -1, 3, 1, 3, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 3, 1, 4, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F#0", + .base_fret = 1, + .frets = { -1, -1, 1, 2, 1, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 3, 2, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F#dim", + .base_fret = 1, + .frets = { -1, -1, 1, 2, 1, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 3, 2, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F#dim7", + .base_fret = 1, + .frets = { -1, -1, 1, 2, 1, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 3, 2, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F#7", + .base_fret = 2, + .frets = { 1, 3, 1, 2, 1, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 1, 2, 1, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F#maj7", + .base_fret = 1, + .frets = { -1, -1, 4, 3, 2, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 4, 3, 2, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F#m7", + .base_fret = 2, + .frets = { 1, 3, 1, 1, 1, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 1, 1, 1, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gb", + .base_fret = 2, + .frets = { 1, 3, 3, 2, 1, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 4, 2, 1, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gbm", + .base_fret = 2, + .frets = { 1, 3, 3, 1, 1, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 4, 1, 1, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gb+", + .base_fret = 1, + .frets = { 2, 1, 0, 3, 3, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 0, 4, 4, 3, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gbaug", + .base_fret = 1, + .frets = { 2, 1, 0, 3, 3, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 0, 4, 4, 3, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gbdim", + .base_fret = 5, + .frets = { -1, -1, 3, 1, 3, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 3, 1, 4, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gb0", + .base_fret = 1, + .frets = { -1, -1, 1, 2, 1, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 3, 2, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gbdim", + .base_fret = 1, + .frets = { -1, -1, 1, 2, 1, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 3, 2, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gbdim7", + .base_fret = 1, + .frets = { -1, -1, 1, 2, 1, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 3, 2, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gb7", + .base_fret = 2, + .frets = { 1, 3, 1, 2, 1, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 1, 2, 1, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gbmaj7", + .base_fret = 1, + .frets = { -1, -1, 4, 3, 2, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 4, 3, 2, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gbm7", + .base_fret = 2, + .frets = { 1, 3, 1, 1, 1, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 1, 1, 1, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G", + .base_fret = 1, + .frets = { 3, 2, 0, 0, 0, 3, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 0, 0, 0, 3, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gm", + .base_fret = 3, + .frets = { 1, 3, 3, 1, 1, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 4, 1, 1, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G+", + .base_fret = 5, + .frets = { -1, -1, 1, 4, 4, 3, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 3, 4, 2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gaug", + .base_fret = 5, + .frets = { -1, -1, 1, 4, 4, 3, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 3, 4, 2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gdim", + .base_fret = 6, + .frets = { -1, -1, 3, 1, 3, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 3, 1, 4, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G0", + .base_fret = 2, + .frets = { -1, -1, 1, 2, 1, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 3, 2, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gdim", + .base_fret = 2, + .frets = { -1, -1, 1, 2, 1, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 3, 2, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gdim7", + .base_fret = 2, + .frets = { -1, -1, 1, 2, 1, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 3, 2, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G7", + .base_fret = 1, + .frets = { 3, 2, 0, 0, 0, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 2, 0, 0, 0, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gmaj7", + .base_fret = 2, + .frets = { -1, -1, 4, 3, 2, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 4, 3, 2, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gm7", + .base_fret = 3, + .frets = { 1, 3, 1, 1, 1, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 1, 1, 1, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G#", + .base_fret = 4, + .frets = { 1, 3, 3, 2, 1, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 4, 2, 1, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G#m", + .base_fret = 4, + .frets = { 1, 3, 3, 1, 1, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 4, 1, 1, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G#+", + .base_fret = 1, + .frets = { 0, 3, 2, 1, 1, 0, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 4, 3, 1, 2, 0, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G#aug", + .base_fret = 1, + .frets = { 0, 3, 2, 1, 1, 0, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 4, 3, 1, 2, 0, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G#dim", + .base_fret = 7, + .frets = { -1, -1, 3, 1, 3, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 3, 1, 4, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G#0", + .base_fret = 1, + .frets = { -1, -1, 0, 1, 0, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 0, 1, 0, 2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G#dim", + .base_fret = 1, + .frets = { -1, -1, 0, 1, 0, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 0, 1, 0, 2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G#dim7", + .base_fret = 1, + .frets = { -1, -1, 0, 1, 0, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 0, 1, 0, 2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G#7", + .base_fret = 4, + .frets = { 1, 3, 1, 2, 1, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 1, 2, 1, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G#maj7", + .base_fret = 1, + .frets = { -1, -1, 1, 1, 1, 3, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 1, 1, 3, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G#m7", + .base_fret = 4, + .frets = { 1, 3, 1, 1, 1, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 1, 1, 1, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ab", + .base_fret = 4, + .frets = { 1, 3, 3, 2, 1, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 4, 2, 1, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Abm", + .base_fret = 4, + .frets = { 1, 3, 3, 1, 1, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 4, 1, 1, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ab+", + .base_fret = 1, + .frets = { 0, 3, 2, 1, 1, 0, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 4, 3, 1, 2, 0, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Abaug", + .base_fret = 1, + .frets = { 0, 3, 2, 1, 1, 0, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 4, 3, 1, 2, 0, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Abdim", + .base_fret = 7, + .frets = { -1, -1, 3, 1, 3, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 3, 1, 4, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ab0", + .base_fret = 1, + .frets = { -1, -1, 0, 1, 0, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 0, 1, 0, 2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Abdim", + .base_fret = 1, + .frets = { -1, -1, 0, 1, 0, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 0, 1, 0, 2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Abdim7", + .base_fret = 1, + .frets = { -1, -1, 0, 1, 0, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 0, 1, 0, 2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ab7", + .base_fret = 4, + .frets = { 1, 3, 1, 2, 1, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 1, 2, 1, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Abmaj7", + .base_fret = 1, + .frets = { -1, -1, 1, 1, 1, 3, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 1, 1, 3, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Abm7", + .base_fret = 4, + .frets = { 1, 3, 1, 1, 1, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 1, 1, 1, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A", + .base_fret = 1, + .frets = { -1, 0, 2, 2, 2, 0, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 2, 3, 0, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Am", + .base_fret = 1, + .frets = { -1, 0, 2, 2, 1, 0, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 2, 3, 1, 0, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A+", + .base_fret = 1, + .frets = { -1, 0, 3, 2, 2, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 4, 2, 3, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Aaug", + .base_fret = 1, + .frets = { -1, 0, 3, 2, 2, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 4, 2, 3, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Adim", + .base_fret = 1, + .frets = { -1, 0, 1, 2, 1, -1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 2, 3, 0, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A0", + .base_fret = 1, + .frets = { -1, -1, 1, 2, 1, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 3, 2, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Adim", + .base_fret = 1, + .frets = { -1, -1, 1, 2, 1, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 3, 2, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Adim7", + .base_fret = 1, + .frets = { -1, -1, 1, 2, 1, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 3, 2, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A7", + .base_fret = 1, + .frets = { -1, 0, 2, 0, 2, 0, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 0, 3, 0, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Amaj7", + .base_fret = 1, + .frets = { -1, 0, 2, 1, 2, 0, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 2, 1, 3, 0, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Am7", + .base_fret = 1, + .frets = { -1, 0, 2, 0, 1, 0, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 2, 0, 1, 0, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A#", + .base_fret = 1, + .frets = { 1, 1, 3, 3, 3, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 2, 3, 4, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A#m", + .base_fret = 1, + .frets = { 1, 1, 3, 3, 2, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 3, 4, 2, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A#+", + .base_fret = 1, + .frets = { 2, 1, 0, 3, 3, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 0, 4, 4, 3, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A#aug", + .base_fret = 1, + .frets = { 2, 1, 0, 3, 3, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 0, 4, 4, 3, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A#dim", + .base_fret = 1, + .frets = { -1, 1, 2, 3, 2, -1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 1, 2, 4, 3, 0, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A#0", + .base_fret = 2, + .frets = { -1, -1, 1, 2, 1, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 3, 2, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A#dim", + .base_fret = 2, + .frets = { -1, -1, 1, 2, 1, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 3, 2, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A#dim7", + .base_fret = 2, + .frets = { -1, -1, 1, 2, 1, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 3, 2, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A#7", + .base_fret = 1, + .frets = { -1, 1, 3, 1, 3, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 1, 2, 1, 3, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A#maj7", + .base_fret = 1, + .frets = { -1, 1, 3, 2, 3, -1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 1, 3, 2, 4, 0, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A#m7", + .base_fret = 1, + .frets = { 1, 1, 3, 1, 2, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 3, 1, 2, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bb", + .base_fret = 1, + .frets = { 1, 1, 3, 3, 3, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 2, 3, 4, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bbm", + .base_fret = 1, + .frets = { 1, 1, 3, 3, 2, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 3, 4, 2, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bb+", + .base_fret = 1, + .frets = { 2, 1, 0, 3, 3, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 0, 4, 4, 3, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bbaug", + .base_fret = 1, + .frets = { 2, 1, 0, 3, 3, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 0, 4, 4, 3, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bbdim", + .base_fret = 1, + .frets = { -1, 1, 2, 3, 2, -1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 1, 2, 4, 3, 0, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bb0", + .base_fret = 2, + .frets = { -1, -1, 1, 2, 1, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 3, 2, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bbdim", + .base_fret = 2, + .frets = { -1, -1, 1, 2, 1, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 3, 2, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bbdim7", + .base_fret = 2, + .frets = { -1, -1, 1, 2, 1, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 3, 2, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bb7", + .base_fret = 1, + .frets = { -1, 1, 3, 1, 3, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 1, 2, 1, 3, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bbmaj7", + .base_fret = 1, + .frets = { -1, 1, 3, 2, 3, -1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 1, 3, 2, 4, 0, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bbm7", + .base_fret = 1, + .frets = { 1, 1, 3, 1, 2, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 3, 1, 2, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "B", + .base_fret = 2, + .frets = { 1, 1, 3, 3, 3, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 2, 3, 4, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bm", + .base_fret = 2, + .frets = { 1, 1, 3, 3, 2, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 3, 4, 2, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "B+", + .base_fret = 1, + .frets = { -1, 3, 2, 0, 0, -1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 2, 1, 0, 0, 0, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Baug", + .base_fret = 1, + .frets = { -1, 3, 2, 0, 0, -1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 2, 1, 0, 0, 0, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bdim", + .base_fret = 2, + .frets = { -1, 1, 2, 3, 2, -1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 1, 2, 4, 3, 0, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "B0", + .base_fret = 1, + .frets = { -1, -1, 0, 1, 0, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 0, 1, 0, 2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bdim", + .base_fret = 1, + .frets = { -1, -1, 0, 1, 0, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 0, 1, 0, 2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bdim7", + .base_fret = 1, + .frets = { -1, -1, 0, 1, 0, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 0, 1, 0, 2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "B7", + .base_fret = 1, + .frets = { -1, 2, 1, 2, 0, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 2, 1, 3, 0, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bmaj7", + .base_fret = 2, + .frets = { -1, 1, 3, 2, 3, -1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 1, 3, 2, 4, 0, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bm7", + .base_fret = 2, + .frets = { 1, 1, 3, 1, 2, 1, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 3, 1, 2, 1, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C9", + .base_fret = 2, + .frets = { -1, 2, 1, 2, 2, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 2, 1, 3, 3, 3, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C#9", + .base_fret = 3, + .frets = { -1, 2, 1, 2, 2, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 2, 1, 3, 3, 3, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Db9", + .base_fret = 3, + .frets = { -1, 2, 1, 2, 2, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 2, 1, 3, 3, 3, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D9", + .base_fret = 4, + .frets = { -1, 2, 1, 2, 2, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 2, 1, 3, 3, 3, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D#9", + .base_fret = 5, + .frets = { -1, 2, 1, 2, 2, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 2, 1, 3, 3, 3, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Eb9", + .base_fret = 5, + .frets = { -1, 2, 1, 2, 2, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 2, 1, 3, 3, 3, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "E9", + .base_fret = 1, + .frets = { 0, 2, 0, 1, 0, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 2, 0, 1, 0, 3, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F9", + .base_fret = 1, + .frets = { 1, 3, 1, 2, 1, 3, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 1, 2, 1, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F#9", + .base_fret = 2, + .frets = { 1, 3, 1, 2, 1, 3, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 1, 2, 1, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gb9", + .base_fret = 2, + .frets = { 1, 3, 1, 2, 1, 3, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 1, 2, 1, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G9", + .base_fret = 3, + .frets = { 1, 3, 1, 2, 1, 3, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 1, 2, 1, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G#9", + .base_fret = 4, + .frets = { 1, 3, 1, 2, 1, 3, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 1, 2, 1, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ab9", + .base_fret = 4, + .frets = { 1, 3, 1, 2, 1, 3, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 1, 2, 1, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A9", + .base_fret = 5, + .frets = { 1, 3, 1, 2, 1, 3, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 1, 2, 1, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A#9", + .base_fret = 6, + .frets = { 1, 3, 1, 2, 1, 3, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 1, 2, 1, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bb9", + .base_fret = 6, + .frets = { 1, 3, 1, 2, 1, 3, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 1, 2, 1, 4, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "B9", + .base_fret = 1, + .frets = { -1, 2, 1, 2, 2, 2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 2, 1, 3, 3, 3, -2, -2, -2, -2, -2, -2 } + } +}; + +static const struct StringDiagram ukulele_diagrams[] = { + { + .name = "A", + .base_fret = 1, + .frets = { 2, 1, 0, 0, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 0, 0, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Am", + .base_fret = 1, + .frets = { 2, 0, 0, 0, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 0, 0, 0, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A7", + .base_fret = 1, + .frets = { 0, 1, 0, 0, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 1, 0, 0, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Am7", + .base_fret = 1, + .frets = { 0, 0, 0, 0, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 0, 0, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Adim", + .base_fret = 2, + .frets = { 1, 2, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 2, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Amaj7", + .base_fret = 1, + .frets = { 1, 1, 0, 0, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 2, 0, 0, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A6", + .base_fret = 2, + .frets = { 1, 3, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 2, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Asus2", + .base_fret = 2, + .frets = { 1, 3, 4, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 3, 4, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Asus", + .base_fret = 1, + .frets = { 2, 2, 0, 0, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 2, 0, 0, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Asus4", + .base_fret = 1, + .frets = { 2, 2, 0, 0, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 2, 0, 0, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A+", + .base_fret = 1, + .frets = { 2, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Aaug", + .base_fret = 1, + .frets = { 2, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A9", + .base_fret = 1, + .frets = { 0, 1, 0, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 1, 0, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A#", + .base_fret = 1, + .frets = { 3, 2, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 2, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A#m", + .base_fret = 1, + .frets = { 3, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A#7", + .base_fret = 1, + .frets = { 1, 2, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 2, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A#m7", + .base_fret = 1, + .frets = { 1, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A#dim", + .base_fret = 1, + .frets = { 0, 1, 0, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 1, 0, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A#maj7", + .base_fret = 1, + .frets = { 2, 2, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 2, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A#6", + .base_fret = 1, + .frets = { 0, 2, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 2, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A#sus2", + .base_fret = 1, + .frets = { 3, 0, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 0, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A#sus", + .base_fret = 1, + .frets = { 3, 3, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 3, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A#sus4", + .base_fret = 1, + .frets = { 3, 3, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 3, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A#+", + .base_fret = 1, + .frets = { 3, 1, 1, 5, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A#aug", + .base_fret = 1, + .frets = { 3, 1, 1, 5, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A#9", + .base_fret = 1, + .frets = { 1, 2, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 2, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bb", + .base_fret = 1, + .frets = { 3, 2, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 2, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bbm", + .base_fret = 1, + .frets = { 3, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bb7", + .base_fret = 1, + .frets = { 1, 2, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 2, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bbm7", + .base_fret = 1, + .frets = { 1, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bbdim", + .base_fret = 1, + .frets = { 0, 1, 0, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 1, 0, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bbmaj7", + .base_fret = 1, + .frets = { 2, 2, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 2, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bb6", + .base_fret = 1, + .frets = { 0, 2, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 2, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bbsus2", + .base_fret = 1, + .frets = { 3, 0, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 0, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bbsus", + .base_fret = 1, + .frets = { 3, 3, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 3, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bbsus4", + .base_fret = 1, + .frets = { 3, 3, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 3, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bb+", + .base_fret = 1, + .frets = { 3, 1, 1, 5, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bbaug", + .base_fret = 1, + .frets = { 3, 1, 1, 5, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bb9", + .base_fret = 1, + .frets = { 1, 2, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 2, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "B", + .base_fret = 2, + .frets = { 3, 2, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 2, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bm", + .base_fret = 2, + .frets = { 3, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "B7", + .base_fret = 2, + .frets = { 1, 2, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 2, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bm7", + .base_fret = 2, + .frets = { 1, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bdim", + .base_fret = 1, + .frets = { 1, 2, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 2, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bmaj7", + .base_fret = 2, + .frets = { 2, 2, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 2, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "B6", + .base_fret = 1, + .frets = { 1, 3, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 4, 2, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bsus2", + .base_fret = 1, + .frets = { 5, 1, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 4, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bsus", + .base_fret = 2, + .frets = { 3, 3, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 2, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bsus4", + .base_fret = 2, + .frets = { 3, 3, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 2, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "B+", + .base_fret = 1, + .frets = { 0, 3, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 2, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Baug", + .base_fret = 1, + .frets = { 0, 3, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 2, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "B9", + .base_fret = 2, + .frets = { 1, 2, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 3, 2, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C", + .base_fret = 1, + .frets = { 0, 0, 0, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 0, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Cm", + .base_fret = 1, + .frets = { 0, 3, 3, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 1, 2, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C7", + .base_fret = 1, + .frets = { 0, 0, 0, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 0, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Cm7", + .base_fret = 3, + .frets = { 1, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Cdim", + .base_fret = 2, + .frets = { 1, 2, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 2, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Cmaj7", + .base_fret = 1, + .frets = { 0, 0, 0, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 0, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C6", + .base_fret = 1, + .frets = { 0, 0, 0, 0, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 0, 0, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Csus2", + .base_fret = 1, + .frets = { 0, 2, 3, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 1, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Csus", + .base_fret = 1, + .frets = { 0, 0, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Csus4", + .base_fret = 1, + .frets = { 0, 0, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C+", + .base_fret = 1, + .frets = { 1, 0, 0, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 0, 0, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Caug", + .base_fret = 1, + .frets = { 1, 0, 0, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 0, 0, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C9", + .base_fret = 1, + .frets = { 0, 2, 0, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 2, 0, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C#", + .base_fret = 1, + .frets = { 1, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C#m", + .base_fret = 1, + .frets = { 1, 4, 4, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 2, 3, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C#7", + .base_fret = 1, + .frets = { 1, 1, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C#m7", + .base_fret = 1, + .frets = { 2, 2, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 2, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C#dim", + .base_fret = 1, + .frets = { 0, 1, 0, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 1, 0, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C#maj7", + .base_fret = 1, + .frets = { 1, 1, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C#6", + .base_fret = 1, + .frets = { 1, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C#sus2", + .base_fret = 1, + .frets = { 1, 3, 4, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 2, 3, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C#sus", + .base_fret = 1, + .frets = { 1, 1, 2, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 2, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C#sus4", + .base_fret = 1, + .frets = { 1, 1, 2, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 2, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C#+", + .base_fret = 1, + .frets = { 2, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C#aug", + .base_fret = 1, + .frets = { 2, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C#9", + .base_fret = 1, + .frets = { 1, 3, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Db", + .base_fret = 1, + .frets = { 1, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Dbm", + .base_fret = 1, + .frets = { 1, 4, 4, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 2, 3, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Db7", + .base_fret = 1, + .frets = { 1, 1, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Dbm7", + .base_fret = 1, + .frets = { 2, 2, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 2, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Dbdim", + .base_fret = 1, + .frets = { 0, 1, 0, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 1, 0, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Dbmaj7", + .base_fret = 1, + .frets = { 1, 1, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Db6", + .base_fret = 1, + .frets = { 1, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Dbsus2", + .base_fret = 1, + .frets = { 1, 3, 4, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 2, 3, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Dbsus", + .base_fret = 1, + .frets = { 1, 1, 2, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 2, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Dbsus4", + .base_fret = 1, + .frets = { 1, 1, 2, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 2, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Db+", + .base_fret = 1, + .frets = { 2, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Dbaug", + .base_fret = 1, + .frets = { 2, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Db9", + .base_fret = 1, + .frets = { 1, 3, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D", + .base_fret = 1, + .frets = { 2, 2, 2, 0, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 2, 3, 0, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Dm", + .base_fret = 1, + .frets = { 2, 2, 1, 0, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 2, 1, 0, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D7", + .base_fret = 2, + .frets = { 1, 1, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Dm7", + .base_fret = 1, + .frets = { 2, 2, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 2, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ddim", + .base_fret = 1, + .frets = { 1, 2, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 2, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Dmaj7", + .base_fret = 2, + .frets = { 1, 1, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D6", + .base_fret = 2, + .frets = { 1, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Dsus2", + .base_fret = 1, + .frets = { 2, 2, 0, 0, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 2, 0, 0, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Dsus", + .base_fret = 1, + .frets = { 0, 2, 3, 0, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 1, 2, 0, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Dsus4", + .base_fret = 1, + .frets = { 0, 2, 3, 0, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 1, 2, 0, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D+", + .base_fret = 2, + .frets = { 2, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Daug", + .base_fret = 2, + .frets = { 2, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D9", + .base_fret = 2, + .frets = { 1, 3, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D#", + .base_fret = 1, + .frets = { 0, 3, 3, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 2, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D#m", + .base_fret = 1, + .frets = { 3, 3, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 3, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D#7", + .base_fret = 3, + .frets = { 1, 1, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D#m7", + .base_fret = 2, + .frets = { 2, 2, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 2, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D#dim", + .base_fret = 2, + .frets = { 1, 2, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D#maj7", + .base_fret = 3, + .frets = { 1, 1, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 2, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D#6", + .base_fret = 3, + .frets = { 1, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D#sus2", + .base_fret = 1, + .frets = { 3, 3, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 2, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D#sus", + .base_fret = 1, + .frets = { 1, 3, 4, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 3, 4, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D#sus4", + .base_fret = 1, + .frets = { 1, 3, 4, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 3, 4, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D#+", + .base_fret = 1, + .frets = { 0, 3, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 2, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D#aug", + .base_fret = 1, + .frets = { 0, 3, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 2, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D#9", + .base_fret = 1, + .frets = { 0, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Eb", + .base_fret = 1, + .frets = { 0, 3, 3, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 2, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ebm", + .base_fret = 1, + .frets = { 3, 3, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 3, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Eb7", + .base_fret = 3, + .frets = { 1, 1, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ebm7", + .base_fret = 2, + .frets = { 2, 2, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 2, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ebdim", + .base_fret = 2, + .frets = { 1, 2, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ebmaj7", + .base_fret = 3, + .frets = { 1, 1, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 2, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Eb6", + .base_fret = 3, + .frets = { 1, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ebsus2", + .base_fret = 1, + .frets = { 3, 3, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 2, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ebsus", + .base_fret = 1, + .frets = { 1, 3, 4, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 3, 4, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ebsus4", + .base_fret = 1, + .frets = { 1, 3, 4, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 3, 4, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Eb+", + .base_fret = 1, + .frets = { 0, 3, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 2, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ebaug", + .base_fret = 1, + .frets = { 0, 3, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 2, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Eb9", + .base_fret = 1, + .frets = { 0, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "E", + .base_fret = 2, + .frets = { 3, 3, 3, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 3, 4, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Em", + .base_fret = 2, + .frets = { 3, 3, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 3, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "E7", + .base_fret = 1, + .frets = { 1, 2, 0, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 2, 0, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Em7", + .base_fret = 1, + .frets = { 0, 2, 0, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 1, 0, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Edim", + .base_fret = 1, + .frets = { 0, 1, 0, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 1, 0, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Emaj7", + .base_fret = 1, + .frets = { 1, 3, 0, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 0, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "E6", + .base_fret = 4, + .frets = { 1, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Esus2", + .base_fret = 2, + .frets = { 3, 3, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 3, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Esus", + .base_fret = 1, + .frets = { 2, 4, 0, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 4, 0, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Esus4", + .base_fret = 1, + .frets = { 2, 4, 0, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 4, 0, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "E+", + .base_fret = 1, + .frets = { 1, 0, 0, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 0, 0, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Eaug", + .base_fret = 1, + .frets = { 1, 0, 0, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 0, 0, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "E9", + .base_fret = 1, + .frets = { 1, 2, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 2, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F", + .base_fret = 1, + .frets = { 2, 0, 1, 0, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 0, 1, 0, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Fm", + .base_fret = 1, + .frets = { 1, 0, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 0, 2, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F7", + .base_fret = 1, + .frets = { 2, 3, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 3, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Fm7", + .base_fret = 1, + .frets = { 1, 3, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 2, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Fdim", + .base_fret = 1, + .frets = { 1, 2, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 2, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Fmaj7", + .base_fret = 1, + .frets = { 2, 4, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 4, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F6", + .base_fret = 1, + .frets = { 2, 2, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 2, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Fsus2", + .base_fret = 1, + .frets = { 0, 0, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Fsus", + .base_fret = 1, + .frets = { 3, 0, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 0, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Fsus4", + .base_fret = 1, + .frets = { 3, 0, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 0, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F+", + .base_fret = 1, + .frets = { 2, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Faug", + .base_fret = 1, + .frets = { 2, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F9", + .base_fret = 2, + .frets = { 1, 2, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 2, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F#", + .base_fret = 1, + .frets = { 3, 1, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 1, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F#m", + .base_fret = 1, + .frets = { 2, 1, 2, 0, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 3, 0, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F#7", + .base_fret = 1, + .frets = { 3, 4, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 4, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F#m7", + .base_fret = 2, + .frets = { 1, 3, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 2, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F#dim", + .base_fret = 2, + .frets = { 1, 2, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 2, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F#maj7", + .base_fret = 2, + .frets = { 2, 4, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 4, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F#6", + .base_fret = 2, + .frets = { 2, 2, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 2, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F#sus2", + .base_fret = 1, + .frets = { 1, 1, 2, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 2, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F#sus", + .base_fret = 1, + .frets = { 4, 1, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 4, 1, 2, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F#sus4", + .base_fret = 1, + .frets = { 4, 1, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 4, 1, 2, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F#+", + .base_fret = 2, + .frets = { 2, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F#aug", + .base_fret = 2, + .frets = { 2, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F#9", + .base_fret = 3, + .frets = { 1, 2, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 2, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gb", + .base_fret = 1, + .frets = { 3, 1, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 1, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gbm", + .base_fret = 1, + .frets = { 2, 1, 2, 0, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 3, 0, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gb7", + .base_fret = 1, + .frets = { 3, 4, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 4, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gbm7", + .base_fret = 2, + .frets = { 1, 3, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 2, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gbdim", + .base_fret = 2, + .frets = { 1, 2, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 2, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gbmaj7", + .base_fret = 2, + .frets = { 2, 4, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 4, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gb6", + .base_fret = 2, + .frets = { 2, 2, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 2, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gbsus2", + .base_fret = 1, + .frets = { 1, 1, 2, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 2, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gbsus", + .base_fret = 1, + .frets = { 4, 1, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 4, 1, 2, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gbsus4", + .base_fret = 1, + .frets = { 4, 1, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 4, 1, 2, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gb+", + .base_fret = 2, + .frets = { 2, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gbaug", + .base_fret = 2, + .frets = { 2, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gb9", + .base_fret = 3, + .frets = { 1, 2, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 2, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G", + .base_fret = 1, + .frets = { 0, 2, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gm", + .base_fret = 1, + .frets = { 0, 2, 3, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 2, 3, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G7", + .base_fret = 1, + .frets = { 0, 2, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 2, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gm7", + .base_fret = 1, + .frets = { 0, 2, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 2, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gdim", + .base_fret = 1, + .frets = { 0, 1, 0, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 1, 0, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gmaj7", + .base_fret = 1, + .frets = { 0, 2, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 1, 2, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G6", + .base_fret = 1, + .frets = { 0, 2, 0, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 1, 0, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gsus2", + .base_fret = 1, + .frets = { 0, 2, 3, 0, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 1, 2, 0, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gsus", + .base_fret = 1, + .frets = { 0, 2, 3, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 1, 2, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gsus4", + .base_fret = 1, + .frets = { 0, 2, 3, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 1, 2, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G+", + .base_fret = 1, + .frets = { 0, 3, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 2, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gaug", + .base_fret = 1, + .frets = { 0, 3, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 2, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G9", + .base_fret = 1, + .frets = { 2, 2, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 3, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G#", + .base_fret = 3, + .frets = { 3, 1, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 1, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G#m", + .base_fret = 1, + .frets = { 1, 3, 4, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 4, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G#7", + .base_fret = 1, + .frets = { 1, 3, 2, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 2, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G#m7", + .base_fret = 1, + .frets = { 1, 3, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 4, 2, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G#dim", + .base_fret = 1, + .frets = { 1, 2, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 2, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G#maj7", + .base_fret = 1, + .frets = { 1, 3, 3, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 2, 3, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G#6", + .base_fret = 1, + .frets = { 1, 3, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 2, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G#sus2", + .base_fret = 1, + .frets = { 1, 3, 4, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 3, 4, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G#sus", + .base_fret = 1, + .frets = { 1, 2, 4, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 3, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G#sus4", + .base_fret = 1, + .frets = { 1, 2, 4, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 3, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G#+", + .base_fret = 1, + .frets = { 1, 0, 0, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 0, 0, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G#aug", + .base_fret = 1, + .frets = { 1, 0, 0, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 0, 0, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G#9", + .base_fret = 1, + .frets = { 1, 0, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 0, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ab", + .base_fret = 3, + .frets = { 3, 1, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 1, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Abm", + .base_fret = 1, + .frets = { 1, 3, 4, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 4, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ab7", + .base_fret = 1, + .frets = { 1, 3, 2, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 2, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Abm7", + .base_fret = 1, + .frets = { 1, 3, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 4, 2, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Abdim", + .base_fret = 1, + .frets = { 1, 2, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 2, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Abmaj7", + .base_fret = 1, + .frets = { 1, 3, 3, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 2, 3, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ab6", + .base_fret = 1, + .frets = { 1, 3, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 2, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Absus2", + .base_fret = 1, + .frets = { 1, 3, 4, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 3, 4, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Absus", + .base_fret = 1, + .frets = { 1, 2, 4, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 3, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Absus4", + .base_fret = 1, + .frets = { 1, 2, 4, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 3, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ab+", + .base_fret = 1, + .frets = { 1, 0, 0, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 0, 0, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Abaug", + .base_fret = 1, + .frets = { 1, 0, 0, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 0, 0, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ab9", + .base_fret = 1, + .frets = { 1, 0, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 0, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + } +}; + +static const struct StringDiagram mandolin_diagrams[] = { + { + .name = "A", + .base_fret = 1, + .frets = { 2, 2, 4, 0, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 3, 0, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Am", + .base_fret = 1, + .frets = { 2, 2, 3, 0, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 2, 0, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A7", + .base_fret = 2, + .frets = { 1, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Am7", + .base_fret = 2, + .frets = { 1, 1, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Aø", + .base_fret = 1, + .frets = { 2, 1, 3, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Am7b5", + .base_fret = 1, + .frets = { 2, 1, 3, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ah", + .base_fret = 1, + .frets = { 2, 1, 3, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A0", + .base_fret = 1, + .frets = { 2, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 4, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Adim", + .base_fret = 1, + .frets = { 2, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 4, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Adim7", + .base_fret = 1, + .frets = { 2, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 4, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Amaj7", + .base_fret = 2, + .frets = { 1, 1, 3, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 3, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A6", + .base_fret = 2, + .frets = { 1, 1, 3, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 3, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Asus2", + .base_fret = 1, + .frets = { 2, 2, 2, 0, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 1, 0, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Asus", + .base_fret = 1, + .frets = { 2, 0, 0, 0, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 0, 0, 0, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Asus4", + .base_fret = 1, + .frets = { 2, 0, 0, 0, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 0, 0, 0, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A+", + .base_fret = 1, + .frets = { 2, 3, 4, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 3, 4, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Aaug", + .base_fret = 1, + .frets = { 2, 3, 4, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 3, 4, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A9", + .base_fret = 2, + .frets = { 1, 4, 3, 6, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 2, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A#", + .base_fret = 1, + .frets = { 3, 0, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 0, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A#m", + .base_fret = 3, + .frets = { 1, 1, 2, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 2, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A#7", + .base_fret = 3, + .frets = { 1, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A#m7", + .base_fret = 3, + .frets = { 1, 1, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A#ø", + .base_fret = 2, + .frets = { 2, 1, 3, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A#m7b5", + .base_fret = 2, + .frets = { 2, 1, 3, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A#h", + .base_fret = 2, + .frets = { 2, 1, 3, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A#0", + .base_fret = 2, + .frets = { 2, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 4, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A#dim", + .base_fret = 2, + .frets = { 2, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 4, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A#dim7", + .base_fret = 2, + .frets = { 2, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 4, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A#maj7", + .base_fret = 1, + .frets = { 3, 0, 0, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 0, 0, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A#6", + .base_fret = 1, + .frets = { 0, 0, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A#sus2", + .base_fret = 3, + .frets = { 1, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A#sus", + .base_fret = 1, + .frets = { 3, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A#sus4", + .base_fret = 1, + .frets = { 3, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A#+", + .base_fret = 1, + .frets = { 3, 0, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 0, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A#aug", + .base_fret = 1, + .frets = { 3, 0, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 0, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "A#9", + .base_fret = 1, + .frets = { 3, 0, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 0, 2, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bb", + .base_fret = 1, + .frets = { 3, 0, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 0, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bbm", + .base_fret = 3, + .frets = { 1, 1, 2, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 2, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bb7", + .base_fret = 3, + .frets = { 1, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bbm7", + .base_fret = 3, + .frets = { 1, 1, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bbø", + .base_fret = 2, + .frets = { 2, 1, 3, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bbm7b5", + .base_fret = 2, + .frets = { 2, 1, 3, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bbh", + .base_fret = 2, + .frets = { 2, 1, 3, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bb0", + .base_fret = 2, + .frets = { 2, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 4, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bbdim", + .base_fret = 2, + .frets = { 2, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 4, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bbdim7", + .base_fret = 2, + .frets = { 2, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 4, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bbmaj7", + .base_fret = 1, + .frets = { 3, 0, 0, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 0, 0, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bb6", + .base_fret = 1, + .frets = { 0, 0, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bbsus2", + .base_fret = 3, + .frets = { 1, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bbsus", + .base_fret = 1, + .frets = { 3, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bbsus4", + .base_fret = 1, + .frets = { 3, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bb+", + .base_fret = 1, + .frets = { 3, 0, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 0, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bbaug", + .base_fret = 1, + .frets = { 3, 0, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 0, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bb9", + .base_fret = 1, + .frets = { 3, 0, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 0, 2, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "B", + .base_fret = 4, + .frets = { 1, 1, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bm", + .base_fret = 1, + .frets = { 4, 0, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 0, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "B7", + .base_fret = 4, + .frets = { 1, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bm7", + .base_fret = 1, + .frets = { 4, 0, 0, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 0, 0, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bø", + .base_fret = 1, + .frets = { 2, 0, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 0, 3, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bm7b5", + .base_fret = 1, + .frets = { 2, 0, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 0, 3, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bh", + .base_fret = 1, + .frets = { 2, 0, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 0, 3, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "B0", + .base_fret = 1, + .frets = { 1, 0, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 0, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bdim", + .base_fret = 1, + .frets = { 1, 0, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 0, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bdim7", + .base_fret = 1, + .frets = { 1, 0, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 0, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bmaj7", + .base_fret = 1, + .frets = { 4, 1, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 4, 1, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "B6", + .base_fret = 1, + .frets = { 1, 1, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bsus2", + .base_fret = 4, + .frets = { 1, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bsus", + .base_fret = 2, + .frets = { 3, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Bsus4", + .base_fret = 2, + .frets = { 3, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "B+", + .base_fret = 1, + .frets = { 4, 1, 2, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 4, 1, 2, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Baug", + .base_fret = 1, + .frets = { 4, 1, 2, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 4, 1, 2, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "B9", + .base_fret = 1, + .frets = { 4, 1, 4, 5, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C", + .base_fret = 1, + .frets = { 5, 2, 3, 0, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 4, 1, 2, 0, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Cm", + .base_fret = 5, + .frets = { 1, 1, 2, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 2, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C7", + .base_fret = 1, + .frets = { 5, 2, 1, 0, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 4, 2, 1, 0, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Cm7", + .base_fret = 5, + .frets = { 1, 1, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Cø", + .base_fret = 1, + .frets = { 3, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 1, 4, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Cm7b5", + .base_fret = 1, + .frets = { 3, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 1, 4, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ch", + .base_fret = 1, + .frets = { 3, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 1, 4, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C0", + .base_fret = 1, + .frets = { 2, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 4, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Cdim", + .base_fret = 1, + .frets = { 2, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 4, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Cdim7", + .base_fret = 1, + .frets = { 2, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 4, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Cmaj7", + .base_fret = 2, + .frets = { 4, 1, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 4, 1, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C6", + .base_fret = 2, + .frets = { 1, 1, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Csus2", + .base_fret = 1, + .frets = { 5, 0, 3, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 0, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Csus", + .base_fret = 3, + .frets = { 3, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Csus4", + .base_fret = 3, + .frets = { 3, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C+", + .base_fret = 2, + .frets = { 4, 1, 2, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 4, 1, 2, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Caug", + .base_fret = 2, + .frets = { 4, 1, 2, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 4, 1, 2, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C9", + .base_fret = 1, + .frets = { 5, 0, 7, 6, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 0, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C#", + .base_fret = 1, + .frets = { 6, 3, 4, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 4, 2, 3, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C#m", + .base_fret = 1, + .frets = { 6, 6, 4, 0, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 3, 1, 0, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C#7", + .base_fret = 2, + .frets = { 5, 2, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 4, 2, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C#m7", + .base_fret = 6, + .frets = { 1, 1, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C#ø", + .base_fret = 2, + .frets = { 3, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 1, 4, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C#m7b5", + .base_fret = 2, + .frets = { 3, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 1, 4, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C#h", + .base_fret = 2, + .frets = { 3, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 1, 4, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C#0", + .base_fret = 1, + .frets = { 3, 2, 1, 0, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 1, 0, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C#dim", + .base_fret = 1, + .frets = { 3, 2, 1, 0, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 1, 0, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C#dim7", + .base_fret = 1, + .frets = { 3, 2, 1, 0, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 1, 0, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C#maj7", + .base_fret = 3, + .frets = { 4, 1, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 4, 1, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C#6", + .base_fret = 3, + .frets = { 1, 1, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C#sus2", + .base_fret = 1, + .frets = { 1, 1, 4, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C#sus", + .base_fret = 4, + .frets = { 3, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C#sus4", + .base_fret = 4, + .frets = { 3, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C#+", + .base_fret = 1, + .frets = { 6, 3, 0, 5, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 4, 1, 0, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C#aug", + .base_fret = 1, + .frets = { 6, 3, 0, 5, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 4, 1, 0, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "C#9", + .base_fret = 3, + .frets = { 4, 1, 4, 5, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Db", + .base_fret = 1, + .frets = { 6, 3, 4, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 4, 2, 3, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Dbm", + .base_fret = 1, + .frets = { 6, 6, 4, 0, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 3, 1, 0, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Db7", + .base_fret = 2, + .frets = { 5, 2, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 4, 2, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Dbm7", + .base_fret = 6, + .frets = { 1, 1, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Dbø", + .base_fret = 2, + .frets = { 3, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 1, 4, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Dbm7b5", + .base_fret = 2, + .frets = { 3, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 1, 4, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Dbh", + .base_fret = 2, + .frets = { 3, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 1, 4, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Db0", + .base_fret = 1, + .frets = { 3, 2, 1, 0, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 1, 0, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Dbdim", + .base_fret = 1, + .frets = { 3, 2, 1, 0, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 1, 0, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Dbdim7", + .base_fret = 1, + .frets = { 3, 2, 1, 0, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 1, 0, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Dbmaj7", + .base_fret = 3, + .frets = { 4, 1, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 4, 1, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Db6", + .base_fret = 3, + .frets = { 1, 1, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Dbsus2", + .base_fret = 1, + .frets = { 1, 1, 4, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Dbsus", + .base_fret = 4, + .frets = { 3, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Dbsus4", + .base_fret = 4, + .frets = { 3, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Db+", + .base_fret = 1, + .frets = { 6, 3, 0, 5, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 4, 1, 0, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Dbaug", + .base_fret = 1, + .frets = { 6, 3, 0, 5, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 4, 1, 0, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Db9", + .base_fret = 3, + .frets = { 4, 1, 4, 5, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D", + .base_fret = 1, + .frets = { 2, 0, 0, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 0, 0, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Dm", + .base_fret = 1, + .frets = { 2, 0, 0, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 0, 0, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D7", + .base_fret = 1, + .frets = { 2, 0, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 0, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Dm7", + .base_fret = 1, + .frets = { 2, 0, 3, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 0, 3, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Dø", + .base_fret = 1, + .frets = { 1, 0, 3, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 0, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Dm7b5", + .base_fret = 1, + .frets = { 1, 0, 3, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 0, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Dh", + .base_fret = 1, + .frets = { 1, 0, 3, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 0, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D0", + .base_fret = 1, + .frets = { 1, 0, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 0, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ddim", + .base_fret = 1, + .frets = { 1, 0, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 0, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ddim7", + .base_fret = 1, + .frets = { 1, 0, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 0, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Dmaj7", + .base_fret = 1, + .frets = { 2, 0, 4, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 0, 4, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D6", + .base_fret = 1, + .frets = { 2, 0, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 0, 2, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Dsus2", + .base_fret = 1, + .frets = { 2, 0, 0, 0, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 0, 0, 0, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Dsus", + .base_fret = 1, + .frets = { 2, 0, 0, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 0, 0, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Dsus4", + .base_fret = 1, + .frets = { 2, 0, 0, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 0, 0, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D+", + .base_fret = 1, + .frets = { 3, 0, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 0, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Daug", + .base_fret = 1, + .frets = { 3, 0, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 0, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D9", + .base_fret = 1, + .frets = { 7, 4, 3, 0, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 4, 2, 1, 0, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D#", + .base_fret = 1, + .frets = { 3, 1, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D#m", + .base_fret = 1, + .frets = { 3, 1, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 1, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D#7", + .base_fret = 1, + .frets = { 3, 1, 4, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 4, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D#m7", + .base_fret = 1, + .frets = { 3, 1, 4, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 1, 4, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D#ø", + .base_fret = 1, + .frets = { 2, 1, 4, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 4, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D#m7b5", + .base_fret = 1, + .frets = { 2, 1, 4, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 4, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D#h", + .base_fret = 1, + .frets = { 2, 1, 4, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 4, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D#0", + .base_fret = 1, + .frets = { 2, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 4, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D#dim", + .base_fret = 1, + .frets = { 2, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 4, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D#dim7", + .base_fret = 1, + .frets = { 2, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 4, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D#maj7", + .base_fret = 1, + .frets = { 3, 1, 5, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 4, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D#6", + .base_fret = 1, + .frets = { 3, 1, 3, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D#sus2", + .base_fret = 1, + .frets = { 3, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D#sus", + .base_fret = 1, + .frets = { 3, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D#sus4", + .base_fret = 1, + .frets = { 3, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D#+", + .base_fret = 1, + .frets = { 0, 1, 2, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 1, 2, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D#aug", + .base_fret = 1, + .frets = { 0, 1, 2, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 1, 2, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "D#9", + .base_fret = 5, + .frets = { 4, 1, 4, 5, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Eb", + .base_fret = 1, + .frets = { 3, 1, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ebm", + .base_fret = 1, + .frets = { 3, 1, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 1, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Eb7", + .base_fret = 1, + .frets = { 3, 1, 4, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 4, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ebm7", + .base_fret = 1, + .frets = { 3, 1, 4, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 1, 4, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ebø", + .base_fret = 1, + .frets = { 2, 1, 4, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 4, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ebm7b5", + .base_fret = 1, + .frets = { 2, 1, 4, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 4, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ebh", + .base_fret = 1, + .frets = { 2, 1, 4, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 4, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Eb0", + .base_fret = 1, + .frets = { 2, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 4, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ebdim", + .base_fret = 1, + .frets = { 2, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 4, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ebdim7", + .base_fret = 1, + .frets = { 2, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 4, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ebmaj7", + .base_fret = 1, + .frets = { 3, 1, 5, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 4, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Eb6", + .base_fret = 1, + .frets = { 3, 1, 3, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ebsus2", + .base_fret = 1, + .frets = { 3, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ebsus", + .base_fret = 1, + .frets = { 3, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ebsus4", + .base_fret = 1, + .frets = { 3, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Eb+", + .base_fret = 1, + .frets = { 0, 1, 2, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 1, 2, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ebaug", + .base_fret = 1, + .frets = { 0, 1, 2, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 1, 2, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Eb9", + .base_fret = 5, + .frets = { 4, 1, 4, 5, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "E", + .base_fret = 1, + .frets = { 1, 2, 2, 0, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 2, 3, 0, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Em", + .base_fret = 1, + .frets = { 0, 2, 2, 0, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 2, 3, 0, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "E7", + .base_fret = 1, + .frets = { 1, 0, 2, 0, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 0, 2, 0, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Em7", + .base_fret = 1, + .frets = { 0, 0, 2, 0, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 2, 0, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Eø", + .base_fret = 1, + .frets = { 0, 0, 1, 0, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 0, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Em7b5", + .base_fret = 1, + .frets = { 0, 0, 1, 0, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 0, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Eh", + .base_fret = 1, + .frets = { 0, 0, 1, 0, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 0, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "E0", + .base_fret = 2, + .frets = { 2, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 4, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Edim", + .base_fret = 2, + .frets = { 2, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 4, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Edim7", + .base_fret = 2, + .frets = { 2, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 4, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Emaj7", + .base_fret = 1, + .frets = { 1, 1, 2, 0, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 2, 0, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "E6", + .base_fret = 1, + .frets = { 4, 6, 4, 0, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 2, 0, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Esus2", + .base_fret = 2, + .frets = { 3, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Esus", + .base_fret = 1, + .frets = { 4, 2, 0, 0, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 1, 0, 0, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Esus4", + .base_fret = 1, + .frets = { 4, 2, 0, 0, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 1, 0, 0, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "E+", + .base_fret = 1, + .frets = { 1, 2, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 2, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Eaug", + .base_fret = 1, + .frets = { 1, 2, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 2, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "E9", + .base_fret = 6, + .frets = { 4, 1, 4, 5, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F", + .base_fret = 1, + .frets = { 2, 3, 0, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 3, 0, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Fm", + .base_fret = 1, + .frets = { 1, 3, 3, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 4, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F7", + .base_fret = 1, + .frets = { 2, 1, 3, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 3, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Fm7", + .base_fret = 1, + .frets = { 1, 1, 3, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 3, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Fø", + .base_fret = 1, + .frets = { 1, 1, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Fm7b5", + .base_fret = 1, + .frets = { 1, 1, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Fh", + .base_fret = 1, + .frets = { 1, 1, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F0", + .base_fret = 1, + .frets = { 1, 0, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 0, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Fdim", + .base_fret = 1, + .frets = { 1, 0, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 0, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Fdim7", + .base_fret = 1, + .frets = { 1, 0, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 0, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Fmaj7", + .base_fret = 1, + .frets = { 2, 2, 3, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 3, 4, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F6", + .base_fret = 1, + .frets = { 2, 0, 3, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 0, 3, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Fsus2", + .base_fret = 1, + .frets = { 0, 3, 3, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 3, 4, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Fsus", + .base_fret = 1, + .frets = { 5, 3, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 4, 2, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Fsus4", + .base_fret = 1, + .frets = { 5, 3, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 4, 2, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F+", + .base_fret = 2, + .frets = { 1, 2, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 2, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Faug", + .base_fret = 2, + .frets = { 1, 2, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 2, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F9", + .base_fret = 7, + .frets = { 4, 1, 4, 5, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F#", + .base_fret = 2, + .frets = { 2, 3, 3, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 3, 4, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F#m", + .base_fret = 2, + .frets = { 1, 3, 3, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 4, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F#7", + .base_fret = 2, + .frets = { 2, 1, 3, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 3, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F#m7", + .base_fret = 2, + .frets = { 1, 1, 3, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 3, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F#ø", + .base_fret = 2, + .frets = { 1, 1, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F#m7b5", + .base_fret = 2, + .frets = { 1, 1, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F#h", + .base_fret = 2, + .frets = { 1, 1, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F#0", + .base_fret = 1, + .frets = { 2, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 4, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F#dim", + .base_fret = 1, + .frets = { 2, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 4, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F#dim7", + .base_fret = 1, + .frets = { 2, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 4, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F#maj7", + .base_fret = 2, + .frets = { 2, 2, 3, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 3, 4, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F#6", + .base_fret = 1, + .frets = { 3, 1, 4, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 1, 4, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F#sus2", + .base_fret = 4, + .frets = { 3, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F#sus", + .base_fret = 2, + .frets = { 5, 3, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 4, 2, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F#sus4", + .base_fret = 2, + .frets = { 5, 3, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 4, 2, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F#+", + .base_fret = 3, + .frets = { 1, 2, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 2, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F#aug", + .base_fret = 3, + .frets = { 1, 2, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 2, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "F#9", + .base_fret = 1, + .frets = { 11, 8, 11, 0, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 3, 0, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gb", + .base_fret = 2, + .frets = { 2, 3, 3, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 3, 4, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gbm", + .base_fret = 2, + .frets = { 1, 3, 3, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 4, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gb7", + .base_fret = 2, + .frets = { 2, 1, 3, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 3, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gbm7", + .base_fret = 2, + .frets = { 1, 1, 3, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 3, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gbø", + .base_fret = 2, + .frets = { 1, 1, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gbm7b5", + .base_fret = 2, + .frets = { 1, 1, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gbh", + .base_fret = 2, + .frets = { 1, 1, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gb0", + .base_fret = 1, + .frets = { 2, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 4, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gbdim", + .base_fret = 1, + .frets = { 2, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 4, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gbdim7", + .base_fret = 1, + .frets = { 2, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 4, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gbmaj7", + .base_fret = 2, + .frets = { 2, 2, 3, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 3, 4, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gb6", + .base_fret = 1, + .frets = { 3, 1, 4, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 1, 4, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gbsus2", + .base_fret = 4, + .frets = { 3, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 3, 1, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gbsus", + .base_fret = 2, + .frets = { 5, 3, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 4, 2, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gbsus4", + .base_fret = 2, + .frets = { 5, 3, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 4, 2, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gb+", + .base_fret = 3, + .frets = { 1, 2, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 2, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gbaug", + .base_fret = 3, + .frets = { 1, 2, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 2, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gb9", + .base_fret = 7, + .frets = { 4, 1, 4, 0, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 3, 0, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G", + .base_fret = 1, + .frets = { 0, 0, 2, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gm", + .base_fret = 1, + .frets = { 0, 0, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G7", + .base_fret = 1, + .frets = { 0, 0, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gm7", + .base_fret = 1, + .frets = { 0, 0, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gø", + .base_fret = 3, + .frets = { 1, 1, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gm7b5", + .base_fret = 3, + .frets = { 1, 1, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gh", + .base_fret = 3, + .frets = { 1, 1, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G0", + .base_fret = 2, + .frets = { 2, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 4, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gdim", + .base_fret = 2, + .frets = { 2, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 4, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gdim7", + .base_fret = 2, + .frets = { 2, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 2, 1, 4, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gmaj7", + .base_fret = 1, + .frets = { 0, 0, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G6", + .base_fret = 1, + .frets = { 0, 0, 2, 0, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 2, 0, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gsus2", + .base_fret = 1, + .frets = { 0, 0, 0, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 0, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gsus", + .base_fret = 1, + .frets = { 0, 0, 3, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gsus4", + .base_fret = 1, + .frets = { 0, 0, 3, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 0, 1, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G+", + .base_fret = 1, + .frets = { 0, 1, 2, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 1, 2, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Gaug", + .base_fret = 1, + .frets = { 0, 1, 2, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 1, 2, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G9", + .base_fret = 1, + .frets = { 0, 3, 0, 7, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 0, 1, 0, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G#", + .base_fret = 1, + .frets = { 1, 1, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G#m", + .base_fret = 1, + .frets = { 1, 1, 2, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 2, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G#7", + .base_fret = 1, + .frets = { 1, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G#m7", + .base_fret = 1, + .frets = { 1, 1, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G#ø", + .base_fret = 1, + .frets = { 1, 0, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 0, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G#m7b5", + .base_fret = 1, + .frets = { 1, 0, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 0, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G#h", + .base_fret = 1, + .frets = { 1, 0, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 0, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G#0", + .base_fret = 1, + .frets = { 1, 0, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 0, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G#dim", + .base_fret = 1, + .frets = { 1, 0, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 0, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G#dim7", + .base_fret = 1, + .frets = { 1, 0, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 0, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G#maj7", + .base_fret = 1, + .frets = { 1, 1, 3, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 3, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G#6", + .base_fret = 1, + .frets = { 1, 1, 3, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 3, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G#sus2", + .base_fret = 1, + .frets = { 1, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G#sus", + .base_fret = 1, + .frets = { 1, 1, 4, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G#sus4", + .base_fret = 1, + .frets = { 1, 1, 4, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G#+", + .base_fret = 1, + .frets = { 1, 2, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 2, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G#aug", + .base_fret = 1, + .frets = { 1, 2, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 2, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "G#9", + .base_fret = 1, + .frets = { 1, 4, 3, 6, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 2, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ab", + .base_fret = 1, + .frets = { 1, 1, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Abm", + .base_fret = 1, + .frets = { 1, 1, 2, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 2, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ab7", + .base_fret = 1, + .frets = { 1, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Abm7", + .base_fret = 1, + .frets = { 1, 1, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Abø", + .base_fret = 1, + .frets = { 1, 0, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 0, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Abm7b5", + .base_fret = 1, + .frets = { 1, 0, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 0, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Abh", + .base_fret = 1, + .frets = { 1, 0, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 0, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ab0", + .base_fret = 1, + .frets = { 1, 0, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 0, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Abdim", + .base_fret = 1, + .frets = { 1, 0, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 0, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Abdim7", + .base_fret = 1, + .frets = { 1, 0, 2, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 0, 3, 2, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Abmaj7", + .base_fret = 1, + .frets = { 1, 1, 3, 3, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 3, 3, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ab6", + .base_fret = 1, + .frets = { 1, 1, 3, 1, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 3, 1, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Absus2", + .base_fret = 1, + .frets = { 1, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 1, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Absus", + .base_fret = 1, + .frets = { 1, 1, 4, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Absus4", + .base_fret = 1, + .frets = { 1, 1, 4, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 1, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ab+", + .base_fret = 1, + .frets = { 1, 2, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 2, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Abaug", + .base_fret = 1, + .frets = { 1, 2, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 2, 3, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + }, + { + .name = "Ab9", + .base_fret = 1, + .frets = { 1, 4, 3, 6, -2, -2, -2, -2, -2, -2, -2, -2 }, + .fingers = { 1, 3, 2, 4, -2, -2, -2, -2, -2, -2, -2, -2 } + } +}; diff --git a/src/lorid.c b/src/lorid.c @@ -0,0 +1,117 @@ +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <string.h> +#include <getopt.h> +#include "types.h" +#include "chordpro.h" +#include "config.h" +#include "out_pdf.h" +#include "util.h" + +int +main(int argc, char *argv[]) +{ + static struct option long_options[] = { + { "print-default-config", no_argument, 0, 'p' }, + { "config", required_argument, 0, 'c' }, + { "output", required_argument, 0, 'o' }, + { "verbose", no_argument, 0, 'v' }, + { "version", no_argument, 0, 'V' }, + { "help", no_argument, 0, 'h' }, + { 0, 0, 0, 0 } + }; + int o, option_index; + const char *chordpro_filepath = NULL; + char *config_filepath = NULL; + char *output = NULL; + struct ChoSong **so, **all_songs = NULL, **songs = NULL; + int s = 0; + FILE *fp; + while ((o = getopt_long(argc, argv, "pc:o:Vvh", long_options, &option_index)) != -1) { + switch(o) { + case 'p': + config_print_default(); + return 0; + case 'c': + config_filepath = emalloc((strlen(optarg)+1) * sizeof(char)); + strcpy(config_filepath, optarg); + break; + case 'o': + output = erealloc(output, (strlen(optarg)+1) * sizeof(char)); + strcpy(output, optarg); + break; + case 'V': + printf(VERSION"\n"); + return 0; + case 'v': + cho_log_enable_info_logs(); + util_log_enable_info_logs(); + break; + case 'h': + return system("man lorid"); + } + } + struct Config *config = config_load(config_filepath); + if (!config) { + LOG_DEBUG("config_load failed."); + return 1; + } + free(config_filepath); + if (argc == optind) { + fp = stdin; + all_songs = cho_songs_parse(fp, NULL, config); + if (!all_songs) { + LOG_DEBUG("cho_songs_parse failed."); + return 1; + } + } else if (argc == optind+1) { + fp = fopen(argv[argc-1], "r"); + if (!fp) { + LOG_DEBUG("fopen failed."); + 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; + for (i = argc-file_count; i<argc; i++) { + fp = fopen(argv[i], "r"); + if (!fp) { + LOG_DEBUG("fopen failed."); + return 1; + } + songs = cho_songs_parse(fp, argv[i], config); + if (!songs) { + LOG_DEBUG("cho_songs_parse failed."); + return 1; + } + fclose(fp); + for (so = songs; *so; s++, so++) { + all_songs = erealloc(all_songs, (s+1) * sizeof(struct ChoSong *)); + all_songs[s] = *so; + } + free(songs); + } + all_songs = erealloc(all_songs, (s+1) * sizeof(struct ChoSong *)); + all_songs[s] = NULL; + 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; + } + util_log(LOG_INFO, "Writing pdf to file: '%s'.", pdf_filename); + free(pdf_filename); + free(output); + cho_songs_free(all_songs); + config_free(config); + return 0; +} diff --git a/src/out_pdf.c b/src/out_pdf.c @@ -0,0 +1,2817 @@ +#include <stdbool.h> +#include <string.h> +#include <unistd.h> +#include <pdfio.h> +#include <pdfio-content.h> +#include <sys/stat.h> +#include <errno.h> +#include <fontconfig/fontconfig.h> +#include <grapheme.h> +#include <math.h> +#include "types.h" +#include "out_pdf.h" +#include "chordpro.h" +#include "config.h" +#include "util.h" +#include "chord_diagram.h" + +static struct Obj **g_fonts = NULL; +static char g_cho_dirpath[PATH_MAX]; +static char g_current_font_name[200]; +static double g_current_font_size; +static int g_current_page_index; +static pdfio_file_t *g_pdf_file = NULL; +static struct Config *g_config = NULL; + +static const char *g_base_fonts[] = { + "Courier", + "Courier-Bold", + "Courier-BoldItalic", + "Courier-Italic", + "Helvetica", + "Helvetica-Bold", + "Helvetica-BoldOblique", + "Helvetica-Oblique", + "Symbol", + "Times-Bold", + "Times-BoldItalic", + "Times-Italic", + "Times-Roman", + "ZapfDingbats" +}; + +static struct Obj * +obj_new(void) +{ + struct Obj *obj = emalloc(sizeof(struct Obj)); + obj->name = NULL; + obj->value = NULL; + return obj; +} + +static void +obj_free(struct Obj *obj) +{ + free(obj->name); + free(obj); +} + +static void +objs_free(struct Obj **objs) +{ + if (!objs) { + return; + } + struct Obj **start = objs; + for (; *objs; objs++) { + obj_free(*objs); + } + free(start); +} + +static pdfio_obj_t * +objs_get_obj(struct Obj **objs, const char *name) +{ + if (!objs) { + return NULL; + } + struct Obj **o; + for (o = objs; *o; o++) { + if (!strcmp((*o)->name, name)) { + return (*o)->value; + } + } + return NULL; +} + +static void +objs_add_obj(struct Obj ***array, struct Obj *obj) +{ + if (!*array) { + *array = erealloc(*array, 2 * sizeof(struct Obj *)); + (*array)[0] = obj; + (*array)[1] = NULL; + } else { + int a; + for (a = 0; (*array)[a]; a++); + *array = erealloc(*array, (a+2) * sizeof(struct Obj *)); + (*array)[a] = obj; + (*array)[a+1] = NULL; + } +} + +pdfio_obj_t * +out_pdf_fnt_obj_get_by_name(const char *name) +{ + int i; + for (i = 0; g_fonts[i]; i++) { + if (!strcmp(g_fonts[i]->name, name)) { + return g_fonts[i]->value; + } + } + printf("name '%s'\n", name); + return NULL; +} + +static char * +fnt_name_create(struct Font *font) +{ + char *name = str_normalize(font->name); + const char *family = cho_font_family_to_config_string(font->family); + const char *style = cho_font_style_to_config_string(font->style); + const char *weight = cho_font_weight_to_config_string(font->weight); + size_t len = strlen(name) + strlen(family) + strlen(style) + strlen(weight); + int n = 0; + int i; + char *fnt_name = emalloc((len + 4) * sizeof(char)); + for (i = 0; name[i]; i++, n++) { + fnt_name[n] = name[i]; + } + fnt_name[n] = '-'; + n++; + for (i = 0; family[i]; i++, n++) { + fnt_name[n] = family[i]; + } + fnt_name[n] = '-'; + n++; + for (i = 0; style[i]; i++, n++) { + fnt_name[n] = style[i]; + } + fnt_name[n] = '-'; + n++; + for (i = 0; weight[i]; i++, n++) { + fnt_name[n] = weight[i]; + } + fnt_name[n] = 0; + free(name); + return fnt_name; +} + +static bool +fonts_add_if_not_in(struct Font ***array, struct Font *font) +{ + if (!*array) { + *array = erealloc(*array, 2 * sizeof(struct Font *)); + (*array)[0] = font; + (*array)[1] = NULL; + return true; + } else { + int a; + for (a = 0; (*array)[a]; a++) { + if ( + !strcmp((*array)[a]->name, font->name) && + (*array)[a]->family == font->family && + (*array)[a]->style == font->style && + (*array)[a]->weight == font->weight + ) { + return false; + } + } + *array = erealloc(*array, (a+2) * sizeof(struct Font *)); + (*array)[a] = font; + (*array)[a+1] = NULL; + return true; + } +} + +static struct Font ** +fonts_get_all(struct ChoSong **songs, struct Config *config) +{ + struct Font **fonts = NULL; + struct Font *font; + struct ChoSong **so; + struct ChoMetadata **m; + struct ChoSection **se; + struct ChoLine **li; + struct ChoLineItemAbove **above; + struct ChoLineItem **it; + struct ChoStyle *style; + bool added; + int i; + for (so = songs; *so; so++) { + for (i = 0; i < TT_LENGTH; i++) { + if ((*so)->present_text_types[i]) { + font = cho_font_copy(config->output->styles[i]->font); + added = fonts_add_if_not_in(&fonts, font); + if (!added) { + cho_font_free(font); + } + } + } + for (m = (*so)->metadata; *m; m++) { + font = cho_font_copy((*m)->style->font); + added = fonts_add_if_not_in(&fonts, font); + if (!added) { + cho_font_free(font); + } + } + for (se = (*so)->sections; *se; se++) { + for (li = (*se)->lines; *li; li++) { + for (above = (*li)->text_above; *above; above++) { + if ((*above)->is_chord) { + style = (*above)->u.chord->style; + } else { + style = (*above)->u.annot->style; + } + if (style->font->name) { + font = cho_font_copy(style->font); + added = fonts_add_if_not_in(&fonts, font); + if (!added) { + cho_font_free(font); + } + } + } + for (it = (*li)->items; *it; it++) { + if ((*it)->is_text) { + style = (*it)->u.text->style; + if (style->font->name) { + font = cho_font_copy(style->font); + added = fonts_add_if_not_in(&fonts, font); + if (!added) { + cho_font_free(font); + } + } + } + } + } + } + } + return fonts; +} + +static int +fontpath_count_fonts(FcChar8 *path) +{ + int count; + FcFontSet *set; + set = FcFontSetCreate(); + if (!FcFileScan(set, NULL, NULL, NULL, path, false)) { + LOG_DEBUG("fontpath_count_fonts failed."); + return -1; + } + count = set->nfont; + FcFontSetDestroy(set); + return count; +} + +static char * +fontpath_find(struct Font *font, enum FontType font_type) +{ + FcObjectSet *obj; + FcPattern *pattern; + char *filepath = NULL; + obj = FcObjectSetBuild(FC_FAMILY, FC_SLANT, FC_WIDTH, FC_WEIGHT, FC_FONTFORMAT, FC_FONT_WRAPPER, FC_FILE, NULL); + pattern = FcPatternCreate(); + FcValue family = { .type = FcTypeString, .u.s = (FcChar8 *)font->name }; + FcPatternAdd(pattern, FC_FAMILY, family, FcFalse); + FcValue font_wrapper = { .type = FcTypeString, .u.s = (FcChar8 *)"SFNT" }; + FcPatternAdd(pattern, FC_FONT_WRAPPER, font_wrapper, FcFalse); + FcValue variable = { .type = FcTypeBool, .u.b = FcFalse }; + FcPatternAdd(pattern, FC_VARIABLE, variable, FcFalse); + FcValue width = { .type = FcTypeInteger, .u.i = FC_WIDTH_NORMAL }; + FcPatternAdd(pattern, FC_WIDTH, width, FcFalse); + /* TODO: Also handle FF_NORMAL, FF_SANS and FF_SERIF, but how? */ + if (font->family == FF_MONOSPACE) { + FcValue spacing = { .type = FcTypeInteger, .u.i = FC_MONO }; + FcPatternAdd(pattern, FC_SPACING, spacing, FcFalse); + } + FcValue type; + type.type = FcTypeString; + if (font_type == FT_OTF) + type.u.s = (FcChar8 *)"CFF"; + else + type.u.s = (FcChar8 *)"TrueType"; + FcPatternAdd(pattern, FC_FONTFORMAT, type, FcFalse); + FcValue style; + style.type = FcTypeInteger; + switch (font->style) { + case FS_ROMAN: + style.u.i = FC_SLANT_ROMAN; + break; + case FS_OBLIQUE: + style.u.i = FC_SLANT_OBLIQUE; + break; + case FS_ITALIC: + style.u.i = FC_SLANT_ITALIC; + break; + default: + util_log(LOG_ERR, "Invalid font style value '%d'.", font->style); + return NULL; + } + FcPatternAdd(pattern, FC_SLANT, style, FcFalse); + FcValue weight; + weight.type = FcTypeInteger; + switch (font->weight) { + case FW_REGULAR: + weight.u.i = FC_WEIGHT_REGULAR; + break; + case FW_BOLD: + weight.u.i = FC_WEIGHT_BOLD; + break; + default: + util_log(LOG_ERR, "Invalid font weight value '%d'.", font->weight); + return NULL; + } + FcPatternAdd(pattern, FC_WEIGHT, weight, FcFalse); + FcFontSet *set = FcFontList(NULL, pattern, obj); + FcChar8 *file; + int i; + for (i = 0; i<set->nfont; i++) { + if (FcPatternGetString(set->fonts[i], FC_FILE, 0, &file) == FcResultMatch) { + if ( + !file_extension_equals((const char *)file, "ttc") && + fontpath_count_fonts(file) == 1 + ) { + filepath = strdup((const char *)file); + break; + } + } + } + FcObjectSetDestroy(obj); + FcPatternDestroy(pattern); + FcFontSetDestroy(set); + return filepath; +} + +static bool +pdf_load_chord_diagram_fonts(void) +{ + struct Obj *fnt; + fnt = obj_new(); + fnt->name = strdup("chord-diagram-regular-font"); + fnt->value = pdfioFileCreateFontObjFromBase(g_pdf_file, "Helvetica"); + objs_add_obj(&g_fonts, fnt); + fnt = obj_new(); + fnt->name = strdup("chord-diagram-symbols"); + fnt->value = pdfioFileCreateFontObjFromFile(g_pdf_file, SYMBOLS_FILEPATH, true); + objs_add_obj(&g_fonts, fnt); + return true; +} + +static const char * +is_base_font(struct Font *font) +{ + int i; + for (i = 0; i<14; i++) { + if (!strcmp(font->name, g_base_fonts[i])) { + return g_base_fonts[i]; + } + } + return NULL; +} + +static bool +font_name_is_path(const char *name) +{ + if (strchr(name, '/')) { + return true; + } + if (file_extension_equals(name, "ttf") || file_extension_equals(name, "otf")) { + return true; + } + return false; +} + +static bool +pdf_load_fonts(struct Font **needed_fonts, struct Config *config) +{ + char *fontpath; + const char *name; + struct Font **f; + struct Obj *fnt; + for (f = needed_fonts; *f; f++) { + if (font_name_is_path((*f)->name)) { + fontpath = filepath_resolve_tilde((*f)->name); + fnt = obj_new(); + fnt->name = fnt_name_create(*f); + fnt->value = pdfioFileCreateFontObjFromFile(g_pdf_file, fontpath, true); + if (!fnt->value) { + LOG_DEBUG("pdfioFileCreateFontObjFromFile failed."); + return false; + } + free(fontpath); + util_log(LOG_INFO, "Loaded font from '%s'.", (*f)->name); + // cho_font_print(*f); + objs_add_obj(&g_fonts, fnt); + } else + if ((name = is_base_font(*f))) { + fnt = obj_new(); + fnt->name = fnt_name_create(*f); + fnt->value = pdfioFileCreateFontObjFromBase(g_pdf_file, name); + if (!fnt->value) { + LOG_DEBUG("pdfioFileCreateFontObjFromBase failed."); + return false; + } + objs_add_obj(&g_fonts, fnt); + } else { + fontpath = fontpath_find(*f, FT_TTF); + if (fontpath) { + fnt = obj_new(); + fnt->name = fnt_name_create(*f); + fnt->value = pdfioFileCreateFontObjFromFile(g_pdf_file, fontpath, true); + if (!fnt->value) { + LOG_DEBUG("pdfioFileCreateFontObjFromFile failed."); + return false; + } + util_log(LOG_INFO, "Loaded font from '%s'.", fontpath); + // cho_font_print(*f); + objs_add_obj(&g_fonts, fnt); + free(fontpath); + } else { + fontpath = fontpath_find(*f, FT_OTF); + if (fontpath) { + fnt = obj_new(); + fnt->name = fnt_name_create(*f); + fnt->value = pdfioFileCreateFontObjFromFile(g_pdf_file, fontpath, true); + if (!fnt->value) { + LOG_DEBUG("pdfioFileCreateFontObjFromFile failed."); + return false; + } + util_log(LOG_INFO, "Loaded font from '%s'.", fontpath); + // cho_font_print(*f); + objs_add_obj(&g_fonts, fnt); + free(fontpath); + } else { + util_log(LOG_ERR, "Didn't find font file for following font:"); + cho_font_print(*f); + return false; + } + } + } + } + if (config->output->diagram->show) { + if (!pdf_load_chord_diagram_fonts()) { + LOG_DEBUG("pdf_load_chord_diagram_fonts failed."); + return false; + } + } + return true; +} + +static char * +pdf_filename_generate_from_songs(struct ChoSong **songs) +{ + char *filename; + char *normalized_title; + const char *title; + int len = cho_song_count(songs); + if (len == 0) + return NULL; + if (len == 1) { + title = cho_metadata_get(songs[0]->metadata, "title"); + if (!title) { + /* INFO: unreachable because the parser already checks the presence of the 'title' directive */ + util_log(LOG_ERR, "Song has no title."); + return NULL; + } + normalized_title = str_normalize(title); + filename = file_extension_replace_or_add(normalized_title, "pdf"); + free(normalized_title); + return filename; + } + return strdup("collection-of-songs.pdf"); +} + +static char * +pdf_filepath_create(struct ChoSong **songs, const char *cho_filepath, const char *out) +{ + char *pdf_filepath = NULL; + char *pdf_filename; + char *tmp; + enum FileType type; + if (cho_filepath) { + pdf_filepath = file_extension_replace_or_add(cho_filepath, "pdf"); + pdf_filename = filepath_basename(pdf_filepath); + } else { + pdf_filename = pdf_filename_generate_from_songs(songs); + if (!pdf_filename) { + LOG_DEBUG("pdf_filename_generate_from_songs failed."); + return NULL; + } + } + if (out) { + type = file_type(out); + switch (type) { + case F_ERROR: + tmp = filepath_dirname(out); + if (file_type(tmp) == F_FOLDER) { + free(pdf_filepath); + free(pdf_filename); + free(tmp); + return strdup(out); + } else { + free(tmp); + util_log(LOG_ERR, "Invalid argument --output/-o value."); + return NULL; + } + break; + case F_REG_FILE: + free(pdf_filepath); + free(pdf_filename); + return strdup(out); + case F_FOLDER: + tmp = filepath_add_ending_slash_if_missing(out); + tmp = erealloc(tmp, (strlen(tmp)+strlen(pdf_filename)+1) * sizeof(char)); + strcat(tmp, pdf_filename); + free(pdf_filename); + free(pdf_filepath); + return tmp; + case F_OTHER: + util_log(LOG_ERR, "Invalid argument --output/-o value. It doesn't refer to a folder or regular file."); + return NULL; + default: + util_log(LOG_ERR, "Invalid enum FileType value '%d'.", type); + return NULL; + } + } else { + if (pdf_filepath) { + free(pdf_filename); + return pdf_filepath; + } + return pdf_filename; + } +} + +static bool +pdf_font_set(pdfio_stream_t *stream, struct Font *font) +{ + static int page_index = 0; + char *name = fnt_name_create(font); + if ( + !strcmp(name, g_current_font_name) && + font->size == g_current_font_size && + g_current_page_index == page_index + ) { + free(name); + return true; + } + if (!pdfioContentSetTextFont(stream, name, font->size)) { + LOG_DEBUG("pdfioContentSetTextFont failed."); + return false; + } + strcpy(g_current_font_name, name); + g_current_font_size = font->size; + page_index = g_current_page_index; + free(name); + return true; +} + +static double +text_width(const char *text, struct ChoStyle *style) +{ + char *name = fnt_name_create(style->font); + pdfio_obj_t *font_obj = out_pdf_fnt_obj_get_by_name(name); + if (!font_obj) { + LOG_DEBUG("out_pdf_fnt_obj_get_by_name failed."); + return -1.0; + } + free(name); + return pdfioContentTextMeasure(font_obj, text, style->font->size); +} + +static double +text_above_width(struct ChoLineItemAbove *above) +{ + double width; + if (above->is_chord) { + char *name; + name = cho_chord_name_generate(above->u.chord); + width = text_width(name, above->u.chord->style); + if (width == ERROR) { + LOG_DEBUG("text_width failed."); + return ERROR; + } + free(name); + } else { + width = text_width(above->u.annot->text, above->u.annot->style); + if (width == ERROR) { + LOG_DEBUG("text_width failed."); + return ERROR; + } + } + return width; +} + +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 int +text_find_fitting( + const char *str, + struct ChoStyle *style, + double x, + double max_width +) +{ + size_t len = strlen(str); + size_t start = len - 1; + char tmp[len+1]; + strcpy((char *)&tmp, str); + double width; + int i; + do { + i = find_whitespace((const char *)&tmp, start); + if (i == -1) { + util_log(LOG_ERR, "Can't split text because no whitespace was found."); + return -1; + } + tmp[i] = 0; + width = text_width((const char *)&tmp, style); + if (width == ERROR) { + LOG_DEBUG("text_width failed."); + return -1; + } + start = i - 1; + } while (x + width > max_width); + return i; +} + +static bool +pdf_draw_line( + pdfio_stream_t *stream, + struct PDFText *text, + double y, + double width, + enum LineLocation line_location +) +{ + double red, green, blue; + switch (line_location) { + case LL_UNDER: + red = text->style->underline_color->red / 255.0; + green = text->style->underline_color->green / 255.0; + blue = text->style->underline_color->blue / 255.0; + y -= 2.0; + break; + case LL_STRIKETHROUGH: + red = text->style->strikethrough_color->red / 255.0; + green = text->style->strikethrough_color->green / 255.0; + blue = text->style->strikethrough_color->blue / 255.0; + y += text->style->font->size * 0.3; + break; + case LL_OVER: + red = text->style->overline_color->red / 255.0; + green = text->style->overline_color->green / 255.0; + blue = text->style->overline_color->blue / 255.0; + y += text->style->font->size * 0.8; + break; + } + if (!pdfioContentPathMoveTo(stream, text->x, y)) { + LOG_DEBUG("pdfioContentPathMoveTo failed."); + return false; + } + if (!pdfioContentPathLineTo(stream, text->x + width, y)) { + LOG_DEBUG("pdfioContentPathLineTo failed."); + return false; + } + if (!pdfioContentSetStrokeColorRGB(stream, red, green, blue)) { + LOG_DEBUG("pdfioContentSetStrokeColorRGB failed."); + return false; + } + if (!pdfioContentStroke(stream)) { + LOG_DEBUG("pdfioContentStroke failed."); + return false; + } + return true; +} + +static bool +pdf_draw_rectangle( + pdfio_stream_t *stream, + struct PDFText *text +) +{ + double height; + double red, green, blue; + red = text->style->background_color->red / 255.0; + green = text->style->background_color->green / 255.0; + blue = text->style->background_color->blue / 255.0; + if (!pdfioContentSetFillColorRGB(stream, red, green, blue)) { + fprintf(stderr, "pdfioContentSetFillColorRGB failed.\n"); + return false; + } + height = (8.0 + text->style->font->size) - text->style->font->size * 0.8; + if (!pdfioContentPathRect(stream, text->x, text->y, text->width, height)) { + fprintf(stderr, "pdfioContentPathRect failed.\n"); + return false; + } + if (!pdfioContentFill(stream, true)) { + fprintf(stderr, "pdfioContentFill failed.\n"); + return false; + } + return true; +} + +static bool +is_purest_white(struct RGBColor *color) +{ + if (color->red == 255 && color->green == 255 && color->blue == 255) { + return true; + } + return false; +} + +static bool +pdf_text_show(pdfio_stream_t *stream, struct PDFText *text) +{ + double red, green, blue; + bool unicode; + if (!pdf_font_set(stream, text->style->font)) { + LOG_DEBUG("pdf_font_set failed."); + return false; + } + unicode = !is_base_font(text->style->font); + if (!is_purest_white(text->style->background_color)) { + if (!pdf_draw_rectangle(stream, text)) { + LOG_DEBUG("draw_rectangle failed."); + return false; + } + } + red = text->style->foreground_color->red / 255.0; + green = text->style->foreground_color->green / 255.0; + blue = text->style->foreground_color->blue / 255.0; + if (!pdfioContentSetFillColorRGB(stream, red, green, blue)) { + LOG_DEBUG("pdfioContentSetFillColorRGB failed."); + return false; + } + if (!pdfioContentTextBegin(stream)) { + LOG_DEBUG("pdfioContentTextBegin failed."); + return false; + } + if (!pdfioContentTextMoveTo(stream, text->x, text->y)) { + LOG_DEBUG("pdfioContentTextMoveTo failed."); + return false; + } + if (!pdfioContentSetTextRise(stream, text->style->rise)) { + LOG_DEBUG("pdfioContentSetTextRise failed."); + return false; + } + if (!pdfioContentTextShow(stream, unicode, text->text)) { + LOG_DEBUG("pdfioContentTextShow failed."); + return false; + } + if (!pdfioContentTextEnd(stream)) { + LOG_DEBUG("pdfioContentTextEnd failed."); + return false; + } + if (text->style->underline_style == LS_SINGLE) { + pdf_draw_line(stream, text, text->y, text->width, LL_UNDER); + } else if (text->style->underline_style == LS_DOUBLE) { + pdf_draw_line(stream, text, text->y, text->width, LL_UNDER); + pdf_draw_line(stream, text, text->y-1.5, text->width, LL_UNDER); + } + if (text->style->strikethrough) { + pdf_draw_line(stream, text, text->y, text->width, LL_STRIKETHROUGH); + } + if (text->style->overline_style == LS_SINGLE) { + pdf_draw_line(stream, text, text->y, text->width, LL_OVER); + } else if (text->style->overline_style == LS_DOUBLE) { + pdf_draw_line(stream, text, text->y, text->width, LL_OVER); + pdf_draw_line(stream, text, text->y+1.5, text->width, LL_OVER); + } + if (text->style->boxed) { + red = text->style->boxed_color->red / 255.0; + green = text->style->boxed_color->green / 255.0; + blue = text->style->boxed_color->blue / 255.0; + if (!pdfioContentSetStrokeColorRGB(stream, red, green, blue)) { + LOG_DEBUG("pdfioContentSetFillColorRGB failed."); + return false; + } + if (!pdfioContentPathRect(stream, text->x - 2.0, text->y - 2.0, text->width + 4.0, text->style->font->size * 0.8 + 4.0)) { + LOG_DEBUG("pdfioContentPathRect failed."); + return false; + } + if (!pdfioContentStroke(stream)) { + LOG_DEBUG("pdfioContentStroke failed."); + return false; + } + } + return true; +} + +static bool +annot_page_link_add( + struct PDFContext *ctx, + struct TocEntry *entry, + int toc_page_count, + int line_count, + double font_size +) +{ + int page_index; + pdfio_rect_t rect; + pdfio_dict_t *annot; + pdfio_array_t *destination; + rect.x1 = MARGIN_HORIZONTAL; + rect.x2 = MARGIN_HORIZONTAL + LINE_WIDTH; + rect.y1 = ctx->y - 2.0; + rect.y2 = ctx->y + (8.0 + font_size) * line_count - font_size * 0.8; + page_index = entry->page_index + toc_page_count; + 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; + } + destination = pdfioArrayCreate(g_pdf_file); + if (!pdfioArrayAppendNumber(destination, page_index)) { + LOG_DEBUG("pdfioArrayAppendNumber failed."); + return false; + } + if (!pdfioArrayAppendName(destination, "FitH")) { + LOG_DEBUG("pdfioArrayAppendName failed."); + return false; + } + // TODO: Is this constant '30.0' correct with different font sizes, etc. ? + // clicking the annotation should show the song including the song title at the top + if (!pdfioArrayAppendNumber(destination, entry->page_y + 30.0)) { + LOG_DEBUG("pdfioArrayAppendNumber failed."); + return false; + } + if (!pdfioDictSetArray(annot, "Dest", destination)) { + LOG_DEBUG("pdfioDictSetArray failed."); + return false; + } + if (!pdfioArrayAppendDict(ctx->content->pages[ctx->page]->annots, annot)) { + LOG_DEBUG("pdfioArrayAppendDict failed."); + return false; + } + return true; +} + +static bool +annot_url_link_add(struct PDFContext *ctx, struct ChoStyle *style, double width) +{ + 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", "URI")) { + LOG_DEBUG("pdfioDictSetName failed."); + return false; + } + if (!pdfioDictSetString(action, "URI", style->href)) { + LOG_DEBUG("pdfioDictSetString failed."); + return false; + } + if (!pdfioDictSetDict(annot, "A", action)) { + LOG_DEBUG("pdfioDictSetDict failed."); + return false; + } + if (!pdfioArrayAppendDict(ctx->content->pages[ctx->page]->annots, annot)) { + LOG_DEBUG("pdfioArrayAppendDict failed."); + return false; + } + return true; +} + +static bool +pdf_set_title(pdfio_file_t *pdf, struct ChoSong **songs) +{ + // INFO: Set pdf title only if a single song exist + if (songs[0] && !songs[1]) { + const char *title; + title = cho_metadata_get(songs[0]->metadata, "title"); + if (title) { + pdfioFileSetTitle(pdf, title); + return true; + } + return false; + } + return true; +} + +static pdfio_stream_t * +pdf_page_create( + pdfio_file_t *pdf, + struct PDFImage **imgs, + pdfio_array_t *annots +) +{ + pdfio_dict_t *page_dict; + pdfio_stream_t *page_stream; + pdfio_array_t *color_array = pdfioArrayCreateColorFromStandard(pdf, 3, PDFIO_CS_ADOBE); + struct Obj **f; + struct PDFImage **i; + page_dict = pdfioDictCreate(pdf); + if (!pdfioPageDictAddColorSpace(page_dict, "rgbcolorspace", color_array)) { + LOG_DEBUG("pdfioPageDictAddColorSpace failed."); + return NULL; + } + if (!pdfioDictSetArray(page_dict, "Annots", annots)) { + LOG_DEBUG("pdfioDictSetArray failed."); + return NULL; + } + if (imgs) { + for (i = imgs; *i; i++) { + if (!pdfioPageDictAddImage(page_dict, (*i)->name, (*i)->obj)) { + LOG_DEBUG("pdfioPageDictAddImage failed."); + return NULL; + } + } + } + for (f = g_fonts; *f; f++) { + if (!pdfioPageDictAddFont(page_dict, (*f)->name, (*f)->value)) { + LOG_DEBUG("pdfioPageDictAddFont failed."); + return NULL; + } + } + page_stream = pdfioFileCreatePage(pdf, page_dict); + if (!pdfioContentSetFillColorSpace(page_stream, "rgbcolorspace")) { + LOG_DEBUG("pdfioContentSetFillColorSpace failed."); + return NULL; + } + if (!pdfioContentSetStrokeColorSpace(page_stream, "rgbcolorspace")) { + LOG_DEBUG("pdfioContentSetStrokeColorSpace failed."); + return NULL; + } + return page_stream; +} + +static double +image_width(struct ChoImage *image, pdfio_obj_t *obj) +{ + double width; + width = pdfioImageGetWidth(obj); + if (image->width) { + if (image->width->type == ST_POINT) { + width = image->width->d; + } else + if (image->width->type == ST_PERCENT) { + width = LINE_WIDTH * image->width->d; + } + } + if (image->width_scale) { + width *= image->width_scale->d; + } + width += image->border; + return width; +} + +static double +image_height(struct ChoImage *image, pdfio_obj_t *obj) +{ + double height; + height = pdfioImageGetHeight(obj); + if (image->height) { + if (image->height->type == ST_POINT) { + height = image->height->d; + } else + if (image->height->type == ST_PERCENT) { + height = LINE_WIDTH * image->height->d; + } + } + if (image->height_scale) { + height *= image->height_scale->d; + } + height += image->border; + return height; +} + +static char * +image_name(struct ChoImage *image) +{ + char tmp[PATH_MAX]; + char *image_path; + struct stat s; + if (strchr(image->src, '/')) { + image_path = image->src; + } else { + strcpy((char *)&tmp, g_cho_dirpath); + strcat((char *)&tmp, "/"); + strcat((char *)&tmp, image->src); + image_path = (char *)&tmp; + } + if (stat(image_path, &s)) { + LOG_DEBUG("stat failed."); + util_log(LOG_ERR, "%s: %s", image_path, strerror(errno)); + return NULL; + } + memset(tmp, 0, PATH_MAX); + sprintf((char *)&tmp, "%ld", s.st_ino); + return strdup(tmp); +} + +static bool +pdf_load_images(struct Obj ***images, pdfio_file_t *file, struct ChoSong **songs) +{ + struct ChoSong **s; + struct ChoSection **se; + struct ChoLine **li; + struct ChoLineItem **it; + int i = 0; + char filepath[PATH_MAX]; + char *name; + char *image_filepath; + for (s = songs; *s; s++) { + for (se = (*s)->sections; *se; se++) { + for (li = (*se)->lines; *li; li++) { + for (it = (*li)->items; *it; it++) { + if (!(*it)->is_text) { + memset(filepath, 0, PATH_MAX); + strcpy((char *)&filepath, g_cho_dirpath); + strcat((char *)&filepath, "/"); + strcat((char *)&filepath, (*it)->u.image->src); + name = image_name((*it)->u.image); + if (!name) { + LOG_DEBUG("image_name failed."); + return false; + } + if (!objs_get_obj(*images, name)) { + *images = erealloc(*images, (i+2) * sizeof(struct Obj *)); + (*images)[i] = obj_new(); + (*images)[i]->name = strdup(name); + if (strchr((*it)->u.image->src, '/')) { + image_filepath = (*it)->u.image->src; + } else { + image_filepath = (char *)&filepath; + } + (*images)[i]->value = pdfioFileCreateImageObjFromFile(file, image_filepath, true); + if (!(*images)[i]->value) { + LOG_DEBUG("pdfioFileCreateImageObjFromFile failed."); + return false; + } + util_log(LOG_INFO, "Loaded image from '%s'.", image_filepath); + (*images)[i+1] = NULL; + } + free(name); + i++; + } + } + } + } + } + return true; +} + +static bool +pdf_get_chords(struct ChoSong *song, struct ChoChord ***chords) +{ + struct ChoSection **se; + struct ChoLine **li; + struct ChoLineItemAbove **above; + for (se = song->sections; *se; se++) { + for (li = (*se)->lines; *li; li++) { + for (above = (*li)->text_above; *above; above++) { + if ((*above)->is_chord) { + if (!cho_chords_has(*chords, (*above)->u.chord)) { + cho_chords_add(chords, (*above)->u.chord); + } + } + } + } + } + return true; +} + +static double +line_width_until_text_above( + struct ChoLineItem **items, + struct ChoLineItemAbove *above, + struct Obj **img_objs, + struct SpaceNeeded *space +) +{ + int i, save_i, save_k; + int k = EMPTY_INT; + int pos = 0; + double width = 0.0; + char *name; + pdfio_obj_t *font_obj; + pdfio_obj_t *obj; + struct ChoText *text; + for (i = 0; items[i]; i++) { + if (items[i]->is_text) { + for (k = 0; items[i]->u.text->text[k]; k++) { + if (above->position == pos) { + save_i = i; + save_k = k; + goto FOUND; + } + pos++; + } + } + } + /* INFO: If the chord/annotation is the last thing in the line */ + save_i = i; + save_k = k; + FOUND: + if (space) { + space->line_item_index = save_i; + space->text_index = save_k; + } + for (i = 0; items[i] && i <= save_i; i++) { + if (items[i]->is_text) { + text = items[i]->u.text; + name = fnt_name_create(text->style->font); + font_obj = out_pdf_fnt_obj_get_by_name(name); + if (!font_obj) { + LOG_DEBUG("out_pdf_fnt_obj_get_by_name failed."); + return ERROR; + } + if (save_i == i) { + char tmp[strlen(text->text)+1]; + strcpy((char *)&tmp, text->text); + tmp[save_k] = 0; + width += pdfioContentTextMeasure(font_obj, (const char *)&tmp, text->style->font->size); + } else { + width += pdfioContentTextMeasure(font_obj, text->text, text->style->font->size); + } + free(name); + } else { + name = image_name(items[i]->u.image); + if (!name) { + LOG_DEBUG("image_name failed."); + return ERROR; + } + obj = objs_get_obj(img_objs, name); + if (!obj) { + LOG_DEBUG("objs_get_obj failed."); + return ERROR; + } + width += image_width(items[i]->u.image, obj); + free(name); + } + } + return width; +} + +static struct PDFImage * +pdf_image_new(void) +{ + struct PDFImage *image = emalloc(sizeof(struct PDFImage)); + image->x = 0.0; + image->y = 0.0; + image->width = 0.0; + image->height = 0.0; + image->name = NULL; + image->obj = NULL; + return image; +} + +static void +pdf_image_free(struct PDFImage *image) +{ + if (!image) { + return; + } + free(image->name); + free(image); +} + +static struct PDFText * +pdf_text_new(void) +{ + struct PDFText *t = emalloc(sizeof(struct PDFText)); + t->text = NULL; + t->style = NULL; + t->x = 0.0; + t->y = 0.0; + t->width = 0.0; + return t; +} + +static void +pdf_text_free(struct PDFText *text) +{ + if (!text) { + return; + } + free(text->text); + cho_style_free(text->style); + free(text); +} + +static void +toc_entry_free(struct TocEntry *entry) +{ + free(entry->title); + free(entry); +} + +static struct PDFPage * +pdf_page_new(void) +{ + struct PDFPage *page = emalloc(sizeof(struct PDFPage)); + page->texts = NULL; + page->images = NULL; + page->diagrams = NULL; + page->annots = pdfioArrayCreate(g_pdf_file); + return page; +} + +static void +pdf_page_free(struct PDFPage *page) +{ + if (!page) { + return; + } + struct PDFText **t; + struct PDFImage **i; + struct ChordDiagram **d; + if ((t = page->texts)) { + for (; *t; t++) { + pdf_text_free(*t); + } + free(page->texts); + } + if ((i = page->images)) { + for (; *i; i++) { + pdf_image_free(*i); + } + free(page->images); + } + if ((d = page->diagrams)) { + for (; *d; d++) { + chord_diagram_free(*d); + } + free(page->diagrams); + } + free(page); +} + +static void +pdf_context_init(struct PDFContext *ctx) +{ + ctx->content = NULL; + ctx->x = MARGIN_HORIZONTAL; + ctx->y = MEDIABOX_HEIGHT - MARGIN_TOP; + ctx->text = 0; + ctx->image = 0; + ctx->diagram = 0; + ctx->page = 0; + ctx->toc_entry = 0; + ctx->spaces = NULL; + ctx->biggest_font_size = 0.0; + ctx->consumed_lyrics = 0; + ctx->prev_added_space = 0.0; + ctx->margin_bottom = MARGIN_BOTTOM; +} + +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; + for (p = content->pages; *p; p++) { + pdf_page_free(*p); + } + free(content->pages); + if ((toc = content->toc)) { + for (; *toc; toc++) { + toc_entry_free(*toc); + } + free(content->toc); + } + free(content); +} + +static void +spaces_free(struct SpaceNeeded **spaces) +{ + if (!spaces) { + return; + } + struct SpaceNeeded **s; + for (s = spaces; *s; s++) { + free(*s); + } + free(spaces); +} + +static bool +calc_space_between_text_above( + struct ChoLineItem **items, + struct ChoLineItemAbove **text_above, + struct Obj **img_objs, + struct SpaceNeeded ***spaces +) +{ + if (!*text_above) { + return true; + } + int sp = 0; + int i; + for (i = 1; text_above[i]; i++) { + struct SpaceNeeded space = { + .line_item_index = 0, + .text_index = 0, + .text_above_index = 0, + .amount = 0.0 + }; + double width_between; + double width_until_cur; + double width_until_prev; + double prev_width; + width_until_cur = line_width_until_text_above(items, text_above[i], img_objs, NULL); + if (width_until_cur == ERROR) { + LOG_DEBUG("line_width_until_text_above failed."); + return false; + } + i--; + width_until_prev = line_width_until_text_above(items, text_above[i], img_objs, &space); + if (width_until_prev == ERROR) { + LOG_DEBUG("line_width_until_text_above failed."); + return false; + } + prev_width = text_above_width(text_above[i]); + if (prev_width == ERROR) { + LOG_DEBUG("text_above_width failed."); + return false; + } + i++; + width_between = width_until_cur - width_until_prev; + if (prev_width + MIN_CHORD_GAP_WIDTH >= width_between) { + space.text_above_index = i; + space.amount = prev_width - width_between + MIN_CHORD_GAP_WIDTH; + *spaces = erealloc(*spaces, (sp+1) * sizeof(struct SpaceNeeded *)); + (*spaces)[sp] = emalloc(sizeof(struct SpaceNeeded)); + *((*spaces)[sp]) = space; + sp++; + } else { + } + } + *spaces = erealloc(*spaces, (sp+1) * sizeof(struct SpaceNeeded *)); + (*spaces)[sp] = NULL; + return true; +} + +static double +item_width(struct ChoLineItem *item, int i, struct SpaceNeeded **spaces, struct Obj **img_objs) +{ + double width; + if (item->is_text) { + width = text_width(item->u.text->text, item->u.text->style); + if (width == ERROR) { + LOG_DEBUG("text_width failed."); + return ERROR; + } + struct SpaceNeeded **s; + if ((s = spaces)) { + for (; *s; s++) { + if ((*s)->line_item_index == i) { + width += (*s)->amount; + } + } + } + } else { + char *name; + pdfio_obj_t *obj; + name = image_name(item->u.image); + if (!name) { + LOG_DEBUG("image_name failed."); + return ERROR; + } + obj = objs_get_obj(img_objs, name); + if (!obj) { + LOG_DEBUG("objs_get_obj failed."); + return ERROR; + } + free(name); + width = image_width(item->u.image, obj); + } + return width; +} + +static struct CharPosition * +items_find_position_to_break_line( + struct ChoLineItem **items, + struct SpaceNeeded **spaces, + struct Obj **img_objs +) +{ + struct CharPosition *pos = emalloc(sizeof(struct CharPosition)); + pos->line_item_index = EMPTY_INT; + pos->text_index = EMPTY_INT; + double width = 0.0; + double d; + struct ChoLineItem **it = items; + int i; + for (i = 0; it[i]; i++) { + d = item_width(it[i], i, spaces, img_objs); + if (d == ERROR) { + LOG_DEBUG("item_width failed."); + return NULL; + } + if (width + d > LINE_WIDTH) { + if (it[i]->is_text) { + pos->line_item_index = i; + pos->text_index = text_find_fitting( + it[i]->u.text->text, + it[i]->u.text->style, + width, + LINE_WIDTH + ); + if (pos->text_index == EMPTY_INT) { + LOG_DEBUG("text_find_fitting failed."); + return NULL; + } + } else { + pos->line_item_index = i; + pos->text_index = -1; + } + return pos; + } else { + width += d; + } + } + return pos; +} + +static double +images_find_biggest_height(struct ChoLineItem **left_items, int line_item_index, struct Obj **img_objs) +{ + struct ChoLineItem **it; + char *name; + pdfio_obj_t *obj; + int i = 0; + double end = line_item_index; + double biggest = 0.0; + double height; + if (end == -1) { + end = 10000; + } + for (it = left_items; *it && i <= end; it++, i++) { + if (!(*it)->is_text) { + name = image_name((*it)->u.image); + if (!name) { + LOG_DEBUG("image_name failed."); + return ERROR; + } + obj = objs_get_obj(img_objs, name); + if (!obj) { + LOG_DEBUG("objs_get_obj failed."); + return ERROR; + } + height = image_height((*it)->u.image, obj); + if (height > biggest) { + biggest = height; + } + free(name); + } + } + return biggest; +} + +static int +text_above_find_index_to_break_line(struct ChoLineItem **items, struct ChoLineItemAbove **text_above, struct CharPosition *pos) +{ + int position = 0; + int i, k; + if (pos->text_index == -1) { + for (i = 0; items[i]; i++) { + if (pos->line_item_index == i) { + goto FOUND; + } + if (items[i]->is_text) { + for (k = 0; items[i]->u.text->text[k]; k++) { + position++; + } + } + } + } else { + for (i = 0; items[i]; i++) { + if (items[i]->is_text) { + for (k = 0; items[i]->u.text->text[k]; k++) { + if (pos->line_item_index == i && pos->text_index == k) { + goto FOUND; + } + position++; + } + } + } + } + return -2; + FOUND: + for (i = 0; text_above[i]; i++) { + if (text_above[i]->position >= position) { + return i; + } + } + return -1; +} + +static void +text_above_update_positions(struct ChoLineItemAbove **aboves, size_t consumed_lyrics) +{ + struct ChoLineItemAbove **a = aboves; + for (a = aboves; *a; a++) { + (*a)->position -= consumed_lyrics + 1; // why plus one? + } +} + +static bool +pdf_texts_add_lyrics( + struct ChoLineItem *item, + struct PDFContext *ctx, + int i +) +{ + struct PDFText ***texts; + struct SpaceNeeded **sp; + double width; + int t = 0; + int c; + texts = &ctx->content->pages[ctx->page]->texts; + *texts = erealloc(*texts, (ctx->text+1) * sizeof(struct PDFText *)); + (*texts)[ctx->text] = pdf_text_new(); + if (!ctx->spaces) { + (*texts)[ctx->text]->text = strdup(item->u.text->text); + (*texts)[ctx->text]->style = cho_style_copy(item->u.text->style); + (*texts)[ctx->text]->x = ctx->x; + (*texts)[ctx->text]->y = ctx->y; + width = text_width(item->u.text->text, item->u.text->style); + if (width == ERROR) { + LOG_DEBUG("text_width failed."); + return false; + } + (*texts)[ctx->text]->width = width; + if (item->u.text->style->href) { + if (!annot_url_link_add(ctx, item->u.text->style, width)) { + LOG_DEBUG("annot_url_link_add failed."); + return false; + } + } + ctx->x += width; + if ((*texts)[ctx->text]->style->font->size > ctx->biggest_font_size) { + ctx->biggest_font_size = (*texts)[ctx->text]->style->font->size; + } + ctx->consumed_lyrics += strlen((*texts)[ctx->text]->text); + ctx->text++; + return true; + } + for (c = 0; item->u.text->text[c]; c++) { + (*texts)[ctx->text]->text = erealloc((*texts)[ctx->text]->text, (t+1) * sizeof(char)); + (*texts)[ctx->text]->text[t] = item->u.text->text[c]; + t++; + for (sp = ctx->spaces; *sp; sp++) { + if ((*sp)->line_item_index == i && (*sp)->text_index == c) { + size_t len = grapheme_next_character_break_utf8(&item->u.text->text[c], 10); + size_t i; + for (i = 0; i<len-1; i++, t++) { + c++; + (*texts)[ctx->text]->text = erealloc((*texts)[ctx->text]->text, (t+1) * sizeof(char)); + (*texts)[ctx->text]->text[t] = item->u.text->text[c]; + } + (*texts)[ctx->text]->text = erealloc((*texts)[ctx->text]->text, (t+1) * sizeof(char)); + (*texts)[ctx->text]->text[t] = 0; + (*texts)[ctx->text]->style = cho_style_copy(item->u.text->style); + (*texts)[ctx->text]->x = ctx->x; + (*texts)[ctx->text]->y = ctx->y; + width = text_width((*texts)[ctx->text]->text, item->u.text->style); + if (width == ERROR) { + LOG_DEBUG("text_width failed."); + return false; + } + (*texts)[ctx->text]->width = width; + if (item->u.text->style->href) { + if (!annot_url_link_add(ctx, item->u.text->style, width)) { + LOG_DEBUG("annot_url_link_add failed."); + return false; + } + } + ctx->x += width; + ctx->x += (*sp)->amount; + t = 0; + ctx->consumed_lyrics += strlen((*texts)[ctx->text]->text); + ctx->text++; + *texts = erealloc(*texts, (ctx->text+1) * sizeof(struct PDFText *)); + (*texts)[ctx->text] = pdf_text_new(); + } + } + } + (*texts)[ctx->text]->text = erealloc((*texts)[ctx->text]->text, (t+1) * sizeof(char)); + (*texts)[ctx->text]->text[t] = 0; + (*texts)[ctx->text]->style = cho_style_copy(item->u.text->style); + (*texts)[ctx->text]->x = ctx->x; + (*texts)[ctx->text]->y = ctx->y; + width = text_width((*texts)[ctx->text]->text, item->u.text->style); + if (width == ERROR) { + LOG_DEBUG("text_width failed."); + return false; + } + (*texts)[ctx->text]->width = width; + if (item->u.text->style->href) { + if (!annot_url_link_add(ctx, item->u.text->style, width)) { + LOG_DEBUG("annot_url_link_add failed."); + return false; + } + } + ctx->x += width; + if ((*texts)[ctx->text]->style->font->size > ctx->biggest_font_size) { + ctx->biggest_font_size = (*texts)[ctx->text]->style->font->size; + } + ctx->consumed_lyrics += strlen((*texts)[ctx->text]->text); + ctx->text++; + return true; +} + +static double +calc_x(double width, enum Alignment align) +{ + if (align == A_CENTER) { + return MARGIN_HORIZONTAL + (LINE_WIDTH - width) / 2; + } + return MARGIN_HORIZONTAL; +} + +static const char * +handle_index(int index, char a, char b, char c) +{ + int i = 0; + static char str[16]; + memset(&str, 0, sizeof(str)); + switch (index) { + case 1: + str[i++] = a; + break; + case 2: + str[i++] = a; + str[i++] = a; + break; + case 3: + str[i++] = a; + str[i++] = a; + str[i++] = a; + break; + case 4: + str[i++] = a; + str[i++] = b; + break; + case 5: + str[i++] = b; + break; + case 6: + str[i++] = b; + str[i++] = a; + break; + case 7: + str[i++] = b; + str[i++] = a; + str[i++] = a; + break; + case 8: + str[i++] = b; + str[i++] = a; + str[i++] = a; + str[i++] = a; + break; + case 9: + str[i++] = a; + str[i++] = c; + } + str[i] = 0; + return str; +} + +static const char * +numeral_system_western_arabic_to_roman(unsigned int n) +{ + if (n > 999) { + util_log(LOG_ERR, "Converting numbers higher than 999 is not supported."); + return NULL; + } + const char *str; + static char roman[64]; + int k; + int r = 0; + memset(&str, 0, sizeof(str)); + char i = 'I'; + char v = 'V'; + char x = 'X'; + char l = 'L'; + char c = 'C'; + char d = 'D'; + char m = 'M'; + int index_0 = n - n / 10 * 10; + int index_1 = (n - n / 100 * 100 - index_0) / 10; + int index_2 = (n - n / 1000 * 1000 - index_0 - index_1) / 100; + if (index_2 > 0) { + str = handle_index(index_2, c, d, m); + for (k = 0; str[k]; k++, r++) { + roman[r] = str[k]; + } + } + if (index_1 > 0) { + str = handle_index(index_1, x, l, c); + for (k = 0; str[k]; k++, r++) { + roman[r] = str[k]; + } + } + if (index_0 > 0) { + str = handle_index(index_0, i, v, x); + for (k = 0; str[k]; k++, r++) { + roman[r] = str[k]; + } + } + roman[r] = 0; + return roman; +} + +static const char * +numeral_system_number_to_str(enum NumeralSystem system, int n) +{ + if (system == NUS_ROMAN) { + const char *str = numeral_system_western_arabic_to_roman((unsigned int)n); + if (!str) { + LOG_DEBUG("numeral_system_western_arabic_to_roman failed."); + return NULL; + } + return str; + } else { + static char str[11+1]; + sprintf((char *)&str, "%d", n); + return str; + } +} + +static bool +pdf_page_add_page_no(struct PDFContext *ctx, enum NumeralSystem numeral_system) +{ + struct PDFText ***texts; + struct ChoStyle *style; + const char *page_no; + double width, x; + + style = cho_style_new(); + style->font->name = strdup(DEFAULT_FONT_FAMILY); + texts = &ctx->content->pages[ctx->page]->texts; + page_no = numeral_system_number_to_str(numeral_system, ctx->page+1); + if (!page_no) { + LOG_DEBUG("numeral_system_number_to_str failed."); + return false; + } + width = text_width(page_no, style); + if (width == ERROR) { + LOG_DEBUG("text_width failed."); + return false; + } + *texts = erealloc(*texts, (ctx->text+1) * sizeof(struct PDFText *)); + (*texts)[ctx->text] = pdf_text_new(); + (*texts)[ctx->text]->text = strdup(page_no); + (*texts)[ctx->text]->style = style; + (*texts)[ctx->text]->x = MEDIABOX_WIDTH - MARGIN_HORIZONTAL / 2 - width; + (*texts)[ctx->text]->y = ctx->margin_bottom / 2; + (*texts)[ctx->text]->width = width; + switch (g_config->output->page_no->align) { + case A_LEFT: + x = MARGIN_HORIZONTAL / 2; + break; + case A_CENTER: + x = MARGIN_HORIZONTAL + (LINE_WIDTH - width) / 2; + break; + case A_RIGHT: + x = MEDIABOX_WIDTH - MARGIN_HORIZONTAL / 2 - width; + break; + default: + util_log(LOG_ERR, "Invalid Alignment enum value '%d'.", g_config->output->page_no->align); + return false; + } + (*texts)[ctx->text]->x = x; + ctx->text++; + return true; +} + +static bool +pdf_page_close_then_add(struct PDFContext *ctx, enum NumeralSystem numeral_system) +{ + ctx->content->pages[ctx->page]->texts = erealloc( + ctx->content->pages[ctx->page]->texts, + (ctx->text+1) * sizeof(struct PDFText *) + ); + ctx->content->pages[ctx->page]->texts[ctx->text] = NULL; + ctx->content->pages[ctx->page]->images = erealloc( + ctx->content->pages[ctx->page]->images, + (ctx->image+1) * sizeof(struct PDFImage *) + ); + ctx->content->pages[ctx->page]->images[ctx->image] = NULL; + ctx->content->pages[ctx->page]->diagrams = erealloc( + ctx->content->pages[ctx->page]->diagrams, + (ctx->diagram+1) * sizeof(struct ChordDiagram *) + ); + ctx->content->pages[ctx->page]->diagrams[ctx->diagram] = NULL; + ctx->text = 0; + ctx->image = 0; + ctx->diagram = 0; + ctx->page++; + ctx->content->pages = erealloc(ctx->content->pages, (ctx->page+1) * sizeof(struct PDFPage *)); + ctx->content->pages[ctx->page] = pdf_page_new(); + ctx->y = MEDIABOX_HEIGHT - MARGIN_TOP; + if (g_config->output->page_no->show) { + if (!pdf_page_add_page_no(ctx, numeral_system)) { + LOG_DEBUG("pdf_page_add_page_no failed."); + return false; + } + } + return true; +} + +static bool +pdf_texts_add_text( + struct PDFContext *ctx, + const char *text, + struct ChoStyle *style, + enum Alignment align, + enum NumeralSystem numeral_system +) +{ + struct PDFText ***texts; + char str[strlen(text)+1]; + double width; + int index; + strcpy((char *)&str, text); + texts = &ctx->content->pages[ctx->page]->texts; + width = text_width(text, style); + if (width == ERROR) { + LOG_DEBUG("text_width failed."); + return false; + } + if (width > LINE_WIDTH) { + char *t = (char *)&str; + while (width > LINE_WIDTH) { + if (ctx->y < ctx->margin_bottom) { + if (!pdf_page_close_then_add(ctx, numeral_system)) { + LOG_DEBUG("pdf_page_close_then_add failed."); + return false; + } + texts = &ctx->content->pages[ctx->page]->texts; + } + index = text_find_fitting(t, style, 0.0, LINE_WIDTH); + if (index == EMPTY_INT) { + LOG_DEBUG("text_find_fitting failed."); + return false; + } + t[index] = 0; + width = text_width(t, style); + if (width == ERROR) { + LOG_DEBUG("text_width failed."); + return false; + } + ctx->x = calc_x(width, align); + *texts = erealloc(*texts, (ctx->text+1) * sizeof(struct PDFText *)); + (*texts)[ctx->text] = pdf_text_new(); + (*texts)[ctx->text]->text = strdup(t); + (*texts)[ctx->text]->style = cho_style_copy(style); + (*texts)[ctx->text]->x = ctx->x; + (*texts)[ctx->text]->y = ctx->y; + (*texts)[ctx->text]->width = width; + if (style->href) { + if (!annot_url_link_add(ctx, style, width)) { + LOG_DEBUG("annot_url_link_add failed."); + return false; + } + } + ctx->text++; + ctx->y -= 8.0 + style->font->size; + t += index+1; + width = text_width(t, style); + if (width == ERROR) { + LOG_DEBUG("text_width failed."); + return false; + } + } + width = text_width(t, style); + if (width == ERROR) { + LOG_DEBUG("text_width failed."); + return false; + } + ctx->x = calc_x(width, align); + *texts = erealloc(*texts, (ctx->text+1) * sizeof(struct PDFText *)); + (*texts)[ctx->text] = pdf_text_new(); + (*texts)[ctx->text]->text = strdup(t); + (*texts)[ctx->text]->style = cho_style_copy(style); + (*texts)[ctx->text]->x = ctx->x; + (*texts)[ctx->text]->y = ctx->y; + (*texts)[ctx->text]->width = width; + if (style->href) { + if (!annot_url_link_add(ctx, style, width)) { + LOG_DEBUG("annot_url_link_add failed."); + return false; + } + } + ctx->text++; + ctx->y -= 8.0 + style->font->size; + } else { + if (ctx->y < ctx->margin_bottom) { + if (!pdf_page_close_then_add(ctx, numeral_system)) { + LOG_DEBUG("pdf_page_close_then_add failed."); + return false; + } + texts = &ctx->content->pages[ctx->page]->texts; + } + ctx->x = calc_x(width, align); + *texts = erealloc(*texts, (ctx->text+1) * sizeof(struct PDFText *)); + (*texts)[ctx->text] = pdf_text_new(); + (*texts)[ctx->text]->text = strdup(text); + (*texts)[ctx->text]->style = cho_style_copy(style); + (*texts)[ctx->text]->x = ctx->x; + (*texts)[ctx->text]->y = ctx->y; + (*texts)[ctx->text]->width = width; + if (style->href) { + if (!annot_url_link_add(ctx, style, width)) { + LOG_DEBUG("annot_url_link_add failed."); + return false; + } + } + ctx->text++; + ctx->y -= 8.0 + style->font->size; + } + return true; +} + +static int +pdf_toc_page_count( + struct TocEntry **entries, + struct ChoStyle *style, + double max_title_width +) +{ + int page = 0; + double y = MEDIABOX_HEIGHT - MARGIN_TOP; + double width; + struct TocEntry **toc; + for (toc = entries; *toc; toc++) { + if (y < MARGIN_BOTTOM) { + y = MEDIABOX_HEIGHT - MARGIN_TOP; + page++; + } + int index; + char tmp[strlen((*toc)->title)+1]; + strcpy((char *)&tmp, (*toc)->title); + width = text_width((*toc)->title, style); + if (width == ERROR) { + LOG_DEBUG("text_width failed."); + return -1; + } + width += MARGIN_HORIZONTAL; + if (width > max_title_width) { + char *t = (char *)&tmp; + while (width > max_title_width) { + index = text_find_fitting(t, style, MARGIN_HORIZONTAL, max_title_width); + if (index == EMPTY_INT) { + LOG_DEBUG("text_find_fitting failed."); + return -1; + } + t[index] = 0; + y -= 8.0 + style->font->size; + t += index + 1; + width = text_width(t, style); + if (width == ERROR) { + LOG_DEBUG("text_width failed."); + return -1; + } + } + y -= 8.0 + style->font->size; + } else { + y -= 8.0 + style->font->size; + } + } + return page + 1; +} + +static const char * +toc_dots_create(double available_width, double dot_width) +{ + available_width -= MARGIN_HORIZONTAL; + int dot_count; + static char dots[1024]; + memset(&dots, 0, sizeof(dots)); + dot_count = (int)floor(available_width / dot_width); + if (dot_count > 1023) { + return NULL; + } + memset(&dots, (int)'.', dot_count); + dots[dot_count+1] = 0; + return dots; +} + +static bool +pdf_texts_add_toc_entry( + struct PDFContext *ctx, + struct TocEntry *entry, + struct ChoStyle *style, + double max_song_title_width, + int toc_page_count, + double dot_width +) +{ + struct PDFText ***texts; + texts = &ctx->content->pages[ctx->page]->texts; + double width, page_no_x, end_of_title_x, width_between_title_and_page_no, available_dots_width; + double page_no_width, dots_width; + int index, line_count; + char tmp[strlen(entry->title)+1]; + char page_no[11+1]; + strcpy((char *)&tmp, entry->title); + width = text_width(entry->title, style); + if (width == ERROR) { + LOG_DEBUG("text_width failed."); + return false; + } + if (width+MARGIN_HORIZONTAL > max_song_title_width) { + char *t = (char *)&tmp; + line_count = 0; + while (width+MARGIN_HORIZONTAL > max_song_title_width) { + if (ctx->y < MARGIN_BOTTOM) { + if (!pdf_page_close_then_add(ctx, NUS_ROMAN)) { + LOG_DEBUG("pdf_page_close_then_add failed."); + return false; + } + texts = &ctx->content->pages[ctx->page]->texts; + } + index = text_find_fitting(t, style, MARGIN_HORIZONTAL, max_song_title_width); + if (index == EMPTY_INT) { + LOG_DEBUG("text_find_fitting failed."); + return false; + } + t[index] = 0; + *texts = erealloc(*texts, (ctx->text+1) * sizeof(struct PDFText *)); + (*texts)[ctx->text] = pdf_text_new(); + (*texts)[ctx->text]->text = strdup(t); + (*texts)[ctx->text]->style = cho_style_copy(style); + (*texts)[ctx->text]->x = MARGIN_HORIZONTAL; + (*texts)[ctx->text]->y = ctx->y; + ctx->y -= 8.0 + style->font->size; + line_count++; + width = text_width(t, style); + if (width == ERROR) { + LOG_DEBUG("text_width failed."); + return false; + } + (*texts)[ctx->text]->width = width; + t += index + 1; + width = text_width(t, style); + if (width == ERROR) { + LOG_DEBUG("text_width failed."); + return false; + } + ctx->text++; + } + if (ctx->y < MARGIN_BOTTOM) { + if (!pdf_page_close_then_add(ctx, NUS_ROMAN)) { + LOG_DEBUG("pdf_page_close_then_add failed."); + return false; + } + texts = &ctx->content->pages[ctx->page]->texts; + } + end_of_title_x = width; + *texts = erealloc(*texts, (ctx->text+1) * sizeof(struct PDFText *)); + (*texts)[ctx->text] = pdf_text_new(); + (*texts)[ctx->text]->text = strdup(t); + (*texts)[ctx->text]->style = cho_style_copy(style); + (*texts)[ctx->text]->x = MARGIN_HORIZONTAL; + (*texts)[ctx->text]->y = ctx->y; + (*texts)[ctx->text]->width = width; + ctx->text++; + sprintf((char *)&page_no, "%d", entry->page_index+1); + width = text_width(page_no, style); + if (width == ERROR) { + LOG_DEBUG("text_width failed."); + return false; + } + + page_no_x = MEDIABOX_WIDTH - width - MARGIN_HORIZONTAL; + width_between_title_and_page_no = page_no_x - end_of_title_x; + available_dots_width = width_between_title_and_page_no - TOC_DOTS_GAP_WIDTH*2; + const char *dots = toc_dots_create(available_dots_width, dot_width); + if (!dots) { + LOG_DEBUG("toc_dots_create failed."); + return false; + } + + *texts = erealloc(*texts, (ctx->text+1) * sizeof(struct PDFText *)); + (*texts)[ctx->text] = pdf_text_new(); + (*texts)[ctx->text]->text = strdup(dots); + (*texts)[ctx->text]->style = cho_style_copy(style); + (*texts)[ctx->text]->x = MARGIN_HORIZONTAL + end_of_title_x + TOC_DOTS_GAP_WIDTH; + (*texts)[ctx->text]->y = ctx->y; + ctx->text++; + + *texts = erealloc(*texts, (ctx->text+1) * sizeof(struct PDFText *)); + (*texts)[ctx->text] = pdf_text_new(); + (*texts)[ctx->text]->text = strdup(page_no); + (*texts)[ctx->text]->style = cho_style_copy(style); + (*texts)[ctx->text]->x = page_no_x; + (*texts)[ctx->text]->y = ctx->y; + (*texts)[ctx->text]->width = width; + line_count++; + if (!annot_page_link_add(ctx, entry, toc_page_count, line_count, style->font->size)) { + LOG_DEBUG("annot_page_link_add"); + return false; + } + ctx->text++; + ctx->y -= 8.0 + style->font->size; + } else { + if (ctx->y < MARGIN_BOTTOM) { + if (!pdf_page_close_then_add(ctx, NUS_ROMAN)) { + LOG_DEBUG("pdf_page_close_then_add failed."); + return false; + } + texts = &ctx->content->pages[ctx->page]->texts; + } + end_of_title_x = width; + *texts = erealloc(*texts, (ctx->text+1) * sizeof(struct PDFText *)); + (*texts)[ctx->text] = pdf_text_new(); + (*texts)[ctx->text]->text = strdup(entry->title); + (*texts)[ctx->text]->style = cho_style_copy(style); + (*texts)[ctx->text]->x = MARGIN_HORIZONTAL; + (*texts)[ctx->text]->y = ctx->y; + width = text_width(entry->title, style); + if (width == ERROR) { + LOG_DEBUG("text_width failed."); + return false; + } + (*texts)[ctx->text]->width = width; + ctx->text++; + sprintf((char *)&page_no, "%d", entry->page_index+1); + page_no_width = text_width(page_no, style); + if (page_no_width == ERROR) { + LOG_DEBUG("text_width failed."); + return false; + } + + page_no_x = MEDIABOX_WIDTH - page_no_width - MARGIN_HORIZONTAL; + width_between_title_and_page_no = page_no_x - end_of_title_x; + available_dots_width = width_between_title_and_page_no - TOC_DOTS_GAP_WIDTH*2; + const char *dots = toc_dots_create(available_dots_width, dot_width); + if (!dots) { + LOG_DEBUG("toc_dots_create failed."); + return false; + } + dots_width = text_width(dots, style); + if (dots_width == ERROR) { + LOG_DEBUG("text_width failed."); + return false; + } + + *texts = erealloc(*texts, (ctx->text+1) * sizeof(struct PDFText *)); + (*texts)[ctx->text] = pdf_text_new(); + (*texts)[ctx->text]->text = strdup(dots); + (*texts)[ctx->text]->style = cho_style_copy(style); + (*texts)[ctx->text]->x = MARGIN_HORIZONTAL + end_of_title_x + TOC_DOTS_GAP_WIDTH; + (*texts)[ctx->text]->y = ctx->y; + (*texts)[ctx->text]->width = dots_width; + ctx->text++; + + *texts = erealloc(*texts, (ctx->text+1) * sizeof(struct PDFText *)); + (*texts)[ctx->text] = pdf_text_new(); + (*texts)[ctx->text]->text = strdup(page_no); + (*texts)[ctx->text]->style = cho_style_copy(style); + (*texts)[ctx->text]->x = page_no_x; + (*texts)[ctx->text]->y = ctx->y; + (*texts)[ctx->text]->width = page_no_width; + if (!annot_page_link_add(ctx, entry, toc_page_count, 1, style->font->size)) { + LOG_DEBUG("annot_page_link_add"); + return false; + } + ctx->text++; + ctx->y -= 8.0 + style->font->size; + } + return true; +} + +static bool +pdf_toc_create( + struct PDFContent **toc_content, + struct PDFContent *pdf_content, + struct Config *config +) +{ + double max_song_title_width, dot_width; + int toc_page_count; + struct PDFContext ctx; + struct PDFText ***texts; + struct ChoStyle *toc_style, *title_style; + struct TocEntry **toc; + + pdf_context_init(&ctx); + 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; + if (config->output->page_no->show) { + if (!pdf_page_add_page_no(&ctx, NUS_ROMAN)) { + LOG_DEBUG("pdf_page_add_page_no failed."); + return false; + } + } + toc_style = config->output->styles[TT_TOC]; + toc = pdf_content->toc; + max_song_title_width = LINE_WIDTH * 0.85; + toc_page_count = pdf_toc_page_count(toc, toc_style, max_song_title_width); + if (toc_page_count == -1) { + LOG_DEBUG("pdf_toc_page_count failed."); + return false; + } + title_style = config->output->styles[TT_TOC_TITLE]; + if (!pdf_texts_add_text(&ctx, config->output->toc->title, title_style, A_CENTER, NUS_ROMAN)) { + LOG_DEBUG("pdf_texts_add_text failed."); + return false; + } + ctx.y -= 30.0; + dot_width = text_width(".", toc_style); + if (dot_width == ERROR) { + LOG_DEBUG("text_width failed."); + return false; + } + for (; *toc; toc++) { + if (!pdf_texts_add_toc_entry(&ctx, *toc, toc_style, max_song_title_width, toc_page_count, dot_width)) { + LOG_DEBUG("pdf_texts_add_toc_entry failed."); + return false; + } + } + texts = &ctx.content->pages[ctx.page]->texts; + *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, + struct ChoSong **songs, + struct Config *config, + struct Obj **img_objs +) +{ + struct ChoMetadata **m; + struct ChoSection **se; + struct ChoLine **li; + struct PDFText ***texts; + struct PDFImage ***imgs; + struct PDFContext ctx; + struct ChordDiagram ***diagrams, **dgrams, **dgrams_begin; + bool show_diagram = config->output->diagram->show; + bool start_song_on_new_page = config->output->start_song_on_new_page; + double width, height; + + pdf_context_init(&ctx); + if (show_diagram) { + ctx.margin_bottom = 150.0; + } + 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; + imgs = &ctx.content->pages[ctx.page]->images; + diagrams = &ctx.content->pages[ctx.page]->diagrams; + if (config->output->page_no->show) { + if (!pdf_page_add_page_no(&ctx, NUS_WESTERN_ARABIC)) { + LOG_DEBUG("pdf_page_add_page_no failed."); + return false; + } + } + int s; + for (s = 0; songs[s]; s++) { + if (show_diagram) { + struct ChoChord **chords = NULL; + if (!pdf_get_chords(songs[s], &chords)) { + LOG_DEBUG("pdf_get_chords failed."); + return false; + } + if (chords) { + qsort(chords, cho_chord_count(chords), sizeof(struct ChoChord *), cho_chord_compare); + dgrams = chord_diagrams_create(config, &chords, songs[s]->diagrams); + dgrams_begin = dgrams; + while (*dgrams) { + *diagrams = erealloc(*diagrams, (ctx.diagram+1) * sizeof(struct ChordDiagram *)); + (*diagrams)[ctx.diagram] = *dgrams; + ctx.diagram++; + dgrams++; + } + free(dgrams_begin); + } + cho_chords_free(chords); + } + for (m = songs[s]->metadata; *m; 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, NUS_WESTERN_ARABIC)) { + LOG_DEBUG("pdf_texts_add_text failed."); + return false; + } + texts = &ctx.content->pages[ctx.page]->texts; + imgs = &ctx.content->pages[ctx.page]->images; + diagrams = &ctx.content->pages[ctx.page]->diagrams; + } + } + for (m = songs[s]->metadata; *m; m++) { + if (!strcmp((*m)->name, "subtitle")) { + /* + INFO: (*m)->style will be ignored and the config style will be + used because the subtitle style can only be manipulated from the + config file + */ + struct ChoStyle *output_style; + output_style = config->output->styles[TT_SUBTITLE]; + if (!pdf_texts_add_text(&ctx, (*m)->value, output_style, A_CENTER, NUS_WESTERN_ARABIC)) { + LOG_DEBUG("pdf_texts_add_text failed."); + return false; + } + texts = &ctx.content->pages[ctx.page]->texts; + imgs = &ctx.content->pages[ctx.page]->images; + diagrams = &ctx.content->pages[ctx.page]->diagrams; + } + } + ctx.y -= 30.0; + for (se = songs[s]->sections; *se; se++) { + if ((*se)->label) { + if (!pdf_texts_add_text(&ctx, (*se)->label->text, (*se)->label->style, A_LEFT, NUS_WESTERN_ARABIC)) { + LOG_DEBUG("pdf_texts_add_text failed."); + return false; + } + texts = &ctx.content->pages[ctx.page]->texts; + imgs = &ctx.content->pages[ctx.page]->images; + diagrams = &ctx.content->pages[ctx.page]->diagrams; + } + for (li = (*se)->lines; *li; li++) { + int item_index; + int text_above_index; + struct CharPosition *pos; + struct ChoLineItemAbove **left_aboves = (*li)->text_above; + struct ChoLineItem **left_items = (*li)->items; + while (*left_aboves || *left_items) { + ctx.consumed_lyrics = 0; + ctx.biggest_font_size = 0.0; + ctx.prev_added_space = 0.0; + char *string; + struct ChoStyle *style; + if (!calc_space_between_text_above(left_items, left_aboves, img_objs, &ctx.spaces)) { + LOG_DEBUG("calc_space_between_text_above failed."); + return false; + } + pos = items_find_position_to_break_line(left_items, ctx.spaces, img_objs); + if (pos->line_item_index == -1) { + item_index = 10000; + text_above_index = 10000; + } else { + item_index = pos->line_item_index; + text_above_index = text_above_find_index_to_break_line(left_items, left_aboves, pos); + if (text_above_index == -2) { + LOG_DEBUG("text_above_find_index_to_break_line failed."); + return false; + } + if (text_above_index == -1) { + text_above_index = 20000; + } + } + bool text_above_exist; + int i; + for (i = 0; left_aboves[i] && i<text_above_index; i++) { + width = line_width_until_text_above(left_items, left_aboves[i], img_objs, NULL); + if (width == ERROR) { + LOG_DEBUG("line_width_until_text_aboave failed."); + return false; + } + ctx.x = MARGIN_HORIZONTAL + width + ctx.prev_added_space; + struct SpaceNeeded **sp; + for (sp = ctx.spaces; *sp; sp++) { + if ((*sp)->text_above_index == i) { + ctx.x += (*sp)->amount; + ctx.prev_added_space += (*sp)->amount; + } + } + *texts = erealloc(*texts, (ctx.text+1) * sizeof(struct PDFText *)); + (*texts)[ctx.text] = pdf_text_new(); + (*texts)[ctx.text]->x = ctx.x; + (*texts)[ctx.text]->y = ctx.y; + if (left_aboves[i]->is_chord) { + string = cho_chord_name_generate(left_aboves[i]->u.chord); + style = cho_style_copy(left_aboves[i]->u.chord->style); + } else { + string = strdup(left_aboves[i]->u.annot->text); + style = cho_style_copy(left_aboves[i]->u.annot->style); + } + (*texts)[ctx.text]->text = string; + (*texts)[ctx.text]->style = style; + (*texts)[ctx.text]->width = text_width(string, style); + if ((*texts)[ctx.text]->width == ERROR) { + LOG_DEBUG("text_width failed."); + return false; + } + if (style->href) { + if (!annot_url_link_add(&ctx, style, (*texts)[ctx.text]->width)) { + LOG_DEBUG("annot_url_link_add failed."); + return false; + } + } + if (style->font->size > ctx.biggest_font_size) { + ctx.biggest_font_size = style->font->size; + } + ctx.text++; + } + height = images_find_biggest_height(left_items, pos->line_item_index, img_objs); + if (height == ERROR) { + LOG_DEBUG("images_find_biggest_height failed."); + return false; + } + text_above_exist = i > 0; + if (text_above_exist) { + left_aboves += i; + if (height > 2.0 + ctx.biggest_font_size) { + ctx.y -= height; + } else { + ctx.y -= 2.0 + ctx.biggest_font_size; + } + } else { + if (height > 0.0) { + ctx.y -= height; + } + } + if (ctx.y < ctx.margin_bottom) { + struct PDFText **tmp = NULL; + int tm = 0; + ctx.y = MEDIABOX_HEIGHT - MARGIN_TOP; + if (text_above_exist) { + /* INFO: chords/annotations and their corresponding lyrics won't be splitted */ + double prev_y = (*texts)[ctx.text-1]->y; + for (int p = ctx.text-1; prev_y == (*texts)[p]->y; p--) { + (*texts)[p]->y = ctx.y; + tmp = erealloc(tmp, (tm+1) * sizeof(struct PDFText *)); + tmp[tm] = (*texts)[p]; + tm++; + *texts = erealloc(*texts, (--ctx.text) * sizeof(struct PDFText *)); + } + } + if (!pdf_page_close_then_add(&ctx, NUS_WESTERN_ARABIC)) { + LOG_DEBUG("pdf_page_close_then_add failed."); + return false; + } + texts = &ctx.content->pages[ctx.page]->texts; + imgs = &ctx.content->pages[ctx.page]->images; + diagrams = &ctx.content->pages[ctx.page]->diagrams; + if (text_above_exist) { + for (int i=0; i<tm; i++) { + *texts = erealloc(*texts, (ctx.text+1) * sizeof(struct PDFText *)); + (*texts)[ctx.text] = tmp[i]; + ctx.text++; + } + free(tmp); + } + if (text_above_exist) { + if (height > 2.0 + ctx.biggest_font_size) { + ctx.y -= height; + } else { + ctx.y -= 2.0 + ctx.biggest_font_size; + } + } else { + ctx.y -= height; + } + } + ctx.biggest_font_size = 0.0; + ctx.x = MARGIN_HORIZONTAL; + i = 0; + while (*left_items && i < item_index) { + if ((*left_items)->is_text) { + if (!pdf_texts_add_lyrics(*left_items, &ctx, i)) { + LOG_DEBUG("pdf_texts_add_lyrics failed."); + return false; + } + texts = &ctx.content->pages[ctx.page]->texts; + imgs = &ctx.content->pages[ctx.page]->images; + diagrams = &ctx.content->pages[ctx.page]->diagrams; + } else { + *imgs = erealloc(*imgs, (ctx.image+1) * sizeof(struct PDFImage *)); + (*imgs)[ctx.image] = pdf_image_new(); + (*imgs)[ctx.image]->name = image_name((*left_items)->u.image); + if (!(*imgs)[ctx.image]->name) { + LOG_DEBUG("image_name failed."); + return false; + } + (*imgs)[ctx.image]->obj = objs_get_obj(img_objs, (*imgs)[ctx.image]->name); + if (!(*imgs)[ctx.image]->obj) { + LOG_DEBUG("objs_get_obj failed."); + return false; + } + (*imgs)[ctx.image]->width = image_width((*left_items)->u.image, (*imgs)[ctx.image]->obj); + (*imgs)[ctx.image]->height = image_height((*left_items)->u.image, (*imgs)[ctx.image]->obj); + (*imgs)[ctx.image]->x = ctx.x; + (*imgs)[ctx.image]->y = ctx.y; + ctx.x += (*imgs)[ctx.image]->width; + ctx.image++; + } + i++; + left_items++; + } + if (pos->line_item_index != -1 && pos->text_index != -1) { + if ((*left_items)->is_text) { + char *tmp; + (*left_items)->u.text->text[pos->text_index] = 0; + tmp = strdup(&(*left_items)->u.text->text[pos->text_index+1]); + if (!pdf_texts_add_lyrics(*left_items, &ctx, i)) { + LOG_DEBUG("pdf_texts_add_lyrics failed."); + return false; + } + free((*left_items)->u.text->text); + (*left_items)->u.text->text = tmp; + texts = &ctx.content->pages[ctx.page]->texts; + imgs = &ctx.content->pages[ctx.page]->images; + diagrams = &ctx.content->pages[ctx.page]->diagrams; + } + } else + if (pos->line_item_index != -1 && pos->text_index == -1) { + if (!(*left_items)->is_text) { + *imgs = erealloc(*imgs, (ctx.image+1) * sizeof(struct PDFImage *)); + (*imgs)[ctx.image] = pdf_image_new(); + (*imgs)[ctx.image]->name = image_name((*left_items)->u.image); + if (!(*imgs)[ctx.image]->name) { + LOG_DEBUG("image_name failed."); + return false; + } + (*imgs)[ctx.image]->obj = objs_get_obj(img_objs, (*imgs)[ctx.image]->name); + if (!(*imgs)[ctx.image]->obj) { + LOG_DEBUG("objs_get_obj failed."); + return false; + } + (*imgs)[ctx.image]->width = image_width((*left_items)->u.image, (*imgs)[ctx.image]->obj); + (*imgs)[ctx.image]->height = image_height((*left_items)->u.image, (*imgs)[ctx.image]->obj); + (*imgs)[ctx.image]->x = ctx.x; + (*imgs)[ctx.image]->y = ctx.y; + ctx.image++; + } + left_items++; + } + ctx.y -= 8.0 + ctx.biggest_font_size; + if (ctx.y < ctx.margin_bottom) { + if (!pdf_page_close_then_add(&ctx, NUS_WESTERN_ARABIC)) { + LOG_DEBUG("pdf_page_close_then_add failed."); + return false; + } + texts = &ctx.content->pages[ctx.page]->texts; + imgs = &ctx.content->pages[ctx.page]->images; + diagrams = &ctx.content->pages[ctx.page]->diagrams; + } + spaces_free(ctx.spaces); + ctx.spaces = NULL; + free(pos); + pos = NULL; + text_above_update_positions(left_aboves, ctx.consumed_lyrics); + } + if ((*li)->btype == BT_PAGE) { + if (!pdf_page_close_then_add(&ctx, NUS_WESTERN_ARABIC)) { + LOG_DEBUG("pdf_page_close_then_add failed."); + return false; + } + texts = &ctx.content->pages[ctx.page]->texts; + imgs = &ctx.content->pages[ctx.page]->images; + diagrams = &ctx.content->pages[ctx.page]->diagrams; + } + } + ctx.y -= SECTION_GAP_WIDTH; + } + if (start_song_on_new_page) { + if (!pdf_page_close_then_add(&ctx, NUS_WESTERN_ARABIC)) { + LOG_DEBUG("pdf_page_close_then_add failed."); + return false; + } + texts = &ctx.content->pages[ctx.page]->texts; + imgs = &ctx.content->pages[ctx.page]->images; + diagrams = &ctx.content->pages[ctx.page]->diagrams; + } + } + *texts = erealloc(*texts, (ctx.text+1) * sizeof(struct PDFText *)); + (*texts)[ctx.text] = NULL; + *imgs = erealloc(*imgs, (ctx.image+1) * sizeof(struct PDFImage *)); + (*imgs)[ctx.image] = NULL; + *diagrams = erealloc(*diagrams, (ctx.diagram+1) * sizeof(struct ChordDiagram *)); + (*diagrams)[ctx.diagram] = NULL; + if (start_song_on_new_page) { + pdf_page_free(ctx.content->pages[ctx.page]); + ctx.content->pages[ctx.page] = NULL; + } else { + 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 bool +pdf_toc_render(struct PDFContent *content, pdfio_file_t *file) +{ + struct PDFPage **pages; + struct PDFText **texts; + pdfio_stream_t *stream; + pages = content->pages; + int p; + for (p = 0; pages[p]; p++) { + g_current_page_index = p; + stream = pdf_page_create(file, NULL, pages[p]->annots); + if (!stream) { + LOG_DEBUG("pdf_page_create failed."); + return false; + } + for (texts = pages[p]->texts; *texts; texts++) { + if (!pdf_text_show(stream, *texts)) { + LOG_DEBUG("pdf_text_show failed."); + return false; + } + } + if (!pdfioStreamClose(stream)) { + LOG_DEBUG("pdfioStreamClose failed."); + return false; + } + } + return true; +} + +static bool +pdf_content_render(struct PDFContent *content, pdfio_file_t *file) +{ + int p; + struct PDFPage **pages; + struct PDFText **texts; + struct PDFImage **imgs; + pdfio_stream_t *stream; + pages = content->pages; + for (p = 0; pages[p]; p++) { + g_current_page_index = p; + stream = pdf_page_create(file, pages[p]->images, pages[p]->annots); + if (!stream) { + LOG_DEBUG("pdf_page_create failed."); + return false; + } + for (texts = pages[p]->texts; *texts; texts++) { + if (!pdf_text_show(stream, *texts)) { + LOG_DEBUG("pdf_text_show failed."); + return false; + } + } + for (imgs = pages[p]->images; *imgs; imgs++) { + if ( + !pdfioContentDrawImage( + stream, + (*imgs)->name, + (*imgs)->x, + (*imgs)->y, + (*imgs)->width, + (*imgs)->height + ) + ) { + LOG_DEBUG("pdfioContentDrawImage failed."); + return false; + } + } + if (pages[p]->diagrams) { + double x = MARGIN_HORIZONTAL; + double y = 40.0; + double size = 50.0; + double padding = 30.0; + struct ChordDiagram **d; + /* TODO: Handle line break when too long */ + for (d = pages[p]->diagrams; *d; d++) { + if ((*d)->show) { + if (!chord_diagram_draw(stream, *d, x, y, size)) { + LOG_DEBUG("chord_diagram_draw failed."); + return false; + } + x += size + padding; + } + } + } + if (!pdfioStreamClose(stream)) { + LOG_DEBUG("pdfioStreamClose failed."); + return false; + } + } + return true; +} + +char * +out_pdf_create( + const char *cho_filepath, + const char *output_folder_or_file, + struct ChoSong **songs, + struct Config *config +) +{ + struct Font **needed_fonts; + struct Obj **img_objs = NULL; + 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 }; + char *dirpath, *pdf_filepath; + + g_config = config; + + memset(&g_current_font_name, 0, sizeof(g_current_font_name)); + memset(&g_cho_dirpath, 0, PATH_MAX); + + if (cho_filepath) { + dirpath = filepath_dirname(cho_filepath); + } else { + dirpath = getcwd(NULL, 0); + } + strcpy((char *)&g_cho_dirpath, dirpath); + free(dirpath); + pdf_filepath = pdf_filepath_create(songs, cho_filepath, output_folder_or_file); + if (!pdf_filepath) { + LOG_DEBUG("pdf_filepath_create failed."); + return NULL; + } + g_pdf_file = pdfioFileCreate(pdf_filepath, "2.0", &media_box_a4, &crop_box, NULL, NULL); + if (!g_pdf_file) { + LOG_DEBUG("pdfioFileCreate failed."); + return NULL; + } + if (!pdf_set_title(g_pdf_file, songs)) { + LOG_DEBUG("pdf_set_title failed."); + goto CLEAN; + } + needed_fonts = fonts_get_all(songs, config); + if (!pdf_load_fonts(needed_fonts, config)) { + LOG_DEBUG("pdf_load_fonts failed."); + goto CLEAN; + } + cho_fonts_free(needed_fonts); + if (!pdf_load_images(&img_objs, g_pdf_file, songs)) { + LOG_DEBUG("pdf_load_images failed."); + goto CLEAN; + } + if (!pdf_content_create(&pdf_content, songs, config, img_objs)) { + LOG_DEBUG("pdf_content_create failed."); + goto CLEAN; + } + if (config->output->toc->show) { + if (!pdf_toc_create(&toc_content, pdf_content, config)) { + LOG_DEBUG("pdf_toc_create failed."); + goto CLEAN; + } + if (!pdf_toc_render(toc_content, g_pdf_file)) { + LOG_DEBUG("pdf_toc_render failed."); + goto CLEAN; + } + } + if (!pdf_content_render(pdf_content, g_pdf_file)) { + LOG_DEBUG("pdf_content_render failed."); + goto CLEAN; + } + objs_free(img_objs); + pdf_content_free(toc_content); + pdf_content_free(pdf_content); + if (!pdfioFileClose(g_pdf_file)) { + LOG_DEBUG("pdfioFileClose failed."); + goto CLEAN; + } + g_pdf_file = NULL; + objs_free(g_fonts); + g_fonts = NULL; + g_current_font_size = 0.0; + g_current_page_index = 0; + g_config = NULL; + return pdf_filepath; + CLEAN: + if (unlink(pdf_filepath)) { + LOG_DEBUG("unlink failed."); + } + return NULL; +} diff --git a/src/out_pdf.h b/src/out_pdf.h @@ -0,0 +1,103 @@ +#include <pdfio.h> +#include "types.h" +#include "chordpro.h" + +#define MEDIABOX_HEIGHT 841.995 +#define MEDIABOX_WIDTH 595.35 +#define MARGIN_TOP 50.0 +#define MARGIN_BOTTOM 50.0 +// #define MARGIN_BOTTOM 180.0 +#define MARGIN_HORIZONTAL 80.0 +#define LINE_WIDTH MEDIABOX_WIDTH - MARGIN_HORIZONTAL * 2 +#define MIN_CHORD_GAP_WIDTH 5.0 +#define SECTION_GAP_WIDTH 10.0 +#define TOC_DOTS_GAP_WIDTH 10.0 + +#define PATH_MAX 4096 + +enum LineLocation { + LL_OVER, + LL_STRIKETHROUGH, + LL_UNDER +}; + +enum FontType { + FT_TTF, + FT_OTF +}; + +enum NumeralSystem { + NUS_WESTERN_ARABIC, + NUS_ROMAN +}; + +struct CharPosition { + int line_item_index; + int text_index; +}; + +struct Obj { + char *name; + pdfio_obj_t *value; +}; + +struct PDFText { + char *text; + struct ChoStyle *style; + double x; + double y; + double width; +}; + +struct PDFImage { + double x; + double y; + double width; + double height; + char *name; + pdfio_obj_t *obj; +}; + +struct PDFPage { + struct PDFText **texts; + struct PDFImage **images; + pdfio_array_t *annots; + struct ChordDiagram **diagrams; +}; + +struct TocEntry { + int page_index; + double page_y; + char *title; +}; + +struct PDFContent { + struct PDFPage **pages; + struct TocEntry **toc; +}; + +struct SpaceNeeded { + int line_item_index; + int text_index; + int text_above_index; + double amount; +}; + +struct PDFContext { + struct PDFContent *content; + double x; + double y; + int text; + int image; + int diagram; + int page; + int toc_entry; + struct SpaceNeeded **spaces; + double biggest_font_size; + size_t consumed_lyrics; + double prev_added_space; + double margin_bottom; +}; + +char *out_pdf_create(const char *cho_filename, const char *output_folder_or_file, struct ChoSong **songs, struct Config *config); +pdfio_obj_t *out_pdf_fnt_obj_get_by_name(const char *name); diff --git a/src/types.h b/src/types.h @@ -0,0 +1,338 @@ +#include <stdint.h> + +#ifndef _TYPES_H_ +#define _TYPES_H_ + +enum TextType : int8_t { + TT_CHORD, + TT_ANNOT, + TT_CHORUS, + TT_FOOTER, + TT_GRID, + TT_TAB, + TT_TOC, + TT_TOC_TITLE, + TT_TEXT, + TT_TITLE, + TT_SUBTITLE, + TT_LABEL, + TT_COMMENT, + TT_COMMENT_ITALIC, + TT_COMMENT_BOX, + TT_LENGTH +}; + +enum Alignment : int8_t { + A_LEFT, + A_CENTER, + A_RIGHT +}; + +enum Anchor { + AN_PAPER, + AN_PAGE, + AN_COLUMN, + AN_LINE, + AN_FLOAT +}; + +enum BreakType : int8_t { + BT_LINE, + BT_PAGE, + BT_COLUMN +}; + +enum ChordDiagramContent : int8_t { + CDC_STRING, + CDC_KEYBOARD +}; + +enum ChordQualifier { + CQ_MIN, + CQ_MAJ, + CQ_AUG, + CQ_DIM +}; + +enum SectionType { + ST_NEWSONG, + ST_CHORUS, + ST_VERSE, + ST_BRIDGE, + ST_TAB, + ST_GRID, + ST_CUSTOM +}; + +enum SizeType { + ST_POINT, + ST_PERCENT, + ST_EM, + ST_EX +}; + +enum FontFamily : int8_t { + FF_NORMAL, + FF_SANS, + FF_SERIF, + FF_MONOSPACE +}; + +enum FontStyle : int8_t { + FS_ROMAN, + FS_OBLIQUE, + FS_ITALIC +}; + +enum FontWeight : int8_t { + FW_REGULAR, + FW_BOLD +}; + +enum LineStyle : int8_t { + LS_SINGLE, + LS_DOUBLE, + LS_NONE +}; + +enum NoteType { + NT_NOTE, + NT_SHARP, + NT_FLAT +}; + +struct FontPresence { + bool name; + bool family; + bool style; + bool weight; + bool size; +}; + +struct ChoStylePresence { + struct FontPresence font; + bool foreground_color; + bool background_color; + bool underline_style; + bool underline_color; + bool overline_style; + bool overline_color; + bool strikethrough; + bool strikethrough_color; + bool boxed; + bool boxed_color; + bool rise; + bool href; +}; + +struct Font { + char *name; + enum FontFamily family; + enum FontStyle style; + enum FontWeight weight; + double size; +}; + +struct RGBColor { + uint8_t red; + uint8_t green; + uint8_t blue; +}; + +struct ChoStyle { + struct Font *font; + struct RGBColor *foreground_color; + struct RGBColor *background_color; + enum LineStyle underline_style; + struct RGBColor *underline_color; + enum LineStyle overline_style; + struct RGBColor *overline_color; + bool strikethrough; + struct RGBColor *strikethrough_color; + bool boxed; + struct RGBColor *boxed_color; + double rise; + char *href; +}; + +struct ChoChord { + struct ChoStyle *style; + bool is_canonical; + char *name; + char *root; + enum ChordQualifier qual; + char *ext; + char *bass; +}; + +struct ChoText { + struct ChoStyle *style; + char *text; +}; + +struct ChoLineItem { + bool is_text; + union { + struct ChoImage *image; + struct ChoText *text; + } u; +}; + +struct ChoLineItemAbove { + int position; + bool is_chord; + union { + struct ChoChord *chord; + struct ChoText *annot; + } u; +}; + +struct ChoLine { + struct ChoLineItemAbove **text_above; + struct ChoLineItem **items; + enum BreakType btype; +}; + +struct ChoMetadata { + char *name; + char *value; + struct ChoStyle *style; +}; + +struct ChoSection { + enum SectionType type; + struct ChoText *label; + struct ChoLine **lines; +}; + +struct ChoSong { + struct ChoMetadata **metadata; + struct ChoSection **sections; + struct ChordDiagram **diagrams; + bool present_text_types[TT_LENGTH]; +}; + +struct ChoImage { + bool is_asset; + char *id; + char *src; + struct Size *width; + struct Size *height; + struct Size *width_scale; + struct Size *height_scale; + enum Alignment align; + double border; + struct Size *spread_space; + char *href; + struct Size *x; + struct Size *y; + enum Anchor anchor; + struct Size *dx; + struct Size *dy; + struct Size *w; + struct Size *h; + bool bbox; +}; + +struct Size { + enum SizeType type; + double d; +}; + +struct StringDiagram { + char *name; + int8_t base_fret; + int8_t frets[12]; + int8_t fingers[12]; +}; + +struct KeyboardDiagram { + char *name; + int8_t keys[24]; +}; + +struct ChordDiagram { + bool show; + struct RGBColor *color; + bool is_string_instrument; + union { + struct StringDiagram *sd; + struct KeyboardDiagram *kd; + } u; +}; + +enum NotationSystem { + NS_COMMON, + NS_GERMAN, + NS_SCANDINAVIAN, + NS_LATIN, + NS_ROMAN, + NS_NASHVILLE, + NS_CUSTOM +}; + +enum ParseMode { + PM_STRICT, + PM_RELAXED +}; + +enum Instrument : int8_t { + INS_GUITAR, + INS_KEYBOARD, + INS_MANDOLIN, + INS_UKULELE +}; + +struct Note { + char *note; + char *sharp; + char *flat; +}; + +struct ConfigChords { + enum NotationSystem notation_system; + enum ParseMode mode; +}; + +struct ConfigChorus { + char *label; + bool quote; +}; + +struct ConfigParser { + struct ConfigChords *chords; + struct Note **notes; +}; + +struct ConfigChordDiagram { + bool show; + enum Instrument instrument; +}; + +struct ConfigToc { + bool show; + char *title; +}; + +struct ConfigPageNo { + bool show; + enum Alignment align; +}; + +struct ConfigOutput { + struct ConfigChorus *chorus; + struct ConfigToc *toc; + struct ConfigChordDiagram *diagram; + struct ConfigPageNo *page_no; + struct ChoStyle **styles; // TODO: Make array of size 7 + struct Note **notes; + enum NotationSystem notation_system; + bool start_song_on_new_page; +}; + +struct Config { + struct ConfigOutput *output; + struct ConfigParser *parser; +}; + +#endif /* _TYPES_H_ */ diff --git a/src/util.c b/src/util.c @@ -0,0 +1,502 @@ +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <stdarg.h> +#include <ctype.h> +#include <string.h> +#include <sys/stat.h> +#include <errno.h> +#include <assert.h> +#include <limits.h> +#include "types.h" +#include "util.h" + +static bool g_show_info_logs = false; + +void +util_log_enable_info_logs(void) +{ + g_show_info_logs = true; +} + +void * +emalloc(size_t size) +{ + void *ptr = malloc(size); + if (!ptr) { + perror("malloc failed"); + exit(1); + } + return ptr; +} + +void * +erealloc(void *ptr, size_t size) +{ + void *tmp = realloc(ptr, size); + if (!tmp) { + perror("realloc failed"); + exit(1); + } + return tmp; +} + +void +util_log(enum LogLevel level, const char *msg, ...) +{ + if (level == LOG_INFO && !g_show_info_logs) { + return; + } +#if COLOR == 1 + const char *log_level = ""; + const char *color = ""; + switch (level) { + case LOG_INFO: + log_level = "INFO"; + color = "37"; + break; + case LOG_WARN: + log_level = "WARN"; + color = "33"; + break; + case LOG_ERR: + log_level = " ERR"; + color = "31"; + break; + case LOG_TODO: + log_level = "TODO"; + color = "34"; + break; + } + fprintf(stderr, "\033[1;%sm%s\033[0m: ", color, log_level); + va_list va; + va_start(va, msg); + vfprintf(stderr, msg, va); + fprintf(stderr, "\n"); +#else + const char *log_level = ""; + switch (level) { + case LOG_INFO: + log_level = "INFO"; + break; + case LOG_WARN: + log_level = "WARN"; + break; + case LOG_ERR: + log_level = " ERR"; + break; + case LOG_TODO: + log_level = "TODO"; + break; + } + fprintf(stderr, "%s: ", log_level); + va_list va; + va_start(va, msg); + vfprintf(stderr, msg, va); + fprintf(stderr, "\n"); +#endif +} + +bool +str_starts_with(const char *str, const char *part) +{ + unsigned int i; + size_t part_len = strlen(part); + if (part_len > strlen(str)) + return false; + for (i=0; i<part_len; i++) { + if (str[i] != part[i]) + return false; + } + return true; +} + +char * +str_normalize(const char *str) +{ + char *normalized = NULL; + char c; + int n = 0; + int i; + for (i = 0; str[i]; i++) { + if (str[i] == ' ' || str[i] == '/' || str[i] == '.') { + normalized = erealloc(normalized, (n+1) * sizeof(char)); + normalized[n] = '-'; + n++; + continue; + } + if (str[i] == '\'' || str[i] == ',') + continue; + c = (char)tolower(str[i]); + normalized = erealloc(normalized, (n+1) * sizeof(char)); + normalized[n] = c; + n++; + } + normalized = erealloc(normalized, (n+1) * sizeof(char)); + normalized[n] = 0; + return normalized; +} + +char * +str_trim(const char *str) +{ + char *trimmed = NULL; + int begin = 0; + int end = 0; + int len = (int)strlen(str); + for (int i=0; i<len; i++) { + if ( + str[i] == ' ' || + str[i] == '\n' || + str[i] == '\t' || + str[i] == '\r' + ) + begin++; + else + break; + } + for (int i=len-1; i>=0; i--) { + if ( + str[i] == ' '|| + str[i] == '\n' || + str[i] == '\t' || + str[i] == '\r' + ) + end++; + else + break; + } + int k = 0; + for (int i=0; i<len; i++) { + if (i >= begin && i < len - end) { + trimmed = erealloc(trimmed, (k+1) * sizeof(char)); + trimmed[k] = str[i]; + k++; + } + } + trimmed = erealloc(trimmed, (k+1) * sizeof(char)); + trimmed[k] = 0; + return trimmed; +} + +char * +str_remove_leading_whitespace(const char *str) +{ + int i = 0; + while (str[i] == ' ' || str[i] == '\t') { + i++; + } + return strdup(&str[i]); +} + +int +str_compare(const char *a, const char *b) +{ + if (a && b) { + return strcmp(a, b); + } else + if (!a && !b) { + return 0; + } else + if (a && !b) { + return 1; + } else + if (!a && b) { + return -1; + } + assert(false); +} + +bool +strs_has(char **strs, const char *str) +{ + if (!strs) { + return false; + } + char **s; + for (s = strs; *s; s++) { + if (!strcmp(*s, str)) { + return true; + } + } + return false; +} + +void +strs_add(char ***strs, const char *str) +{ + int i = 0; + if (*strs) { + char **s; + for (s = *strs; *s; s++, i++); + } + *strs = erealloc(*strs, (i+2) * sizeof(char *)); + (*strs)[i] = strdup(str); + (*strs)[i+1] = NULL; +} + +void +strs_free(char **strs) +{ + if (!strs) { + return; + } + char **s; + for (s = strs; *s; s++) { + free(*s); + } + free(strs); +} + +long +str_to_number(const char *str) +{ + long n; + char *endptr; + n = strtol(str, &endptr, 10); + if (str == endptr) { + return -1; + } + if ((n == LONG_MIN || n == LONG_MAX) && errno == ERANGE) { + return -1; + } + return n; +} + +enum FileType +file_type(const char *path) +{ + struct stat s; + if (stat(path, &s) != 0) { + return F_ERROR; + } + if (S_ISDIR(s.st_mode)) { + return F_FOLDER; + } + if (S_ISREG(s.st_mode)) { + return F_REG_FILE; + } + return F_OTHER; +} + +char * +file_read(const char *filepath) +{ + char *str = NULL; + char buf; + size_t read; + int i = 0; + FILE *fp = fopen(filepath, "r"); + if (!fp) { + LOG_DEBUG("fopen failed."); + return NULL; + } + while (1) { + read = fread(&buf, 1, 1, fp); + if (read == 1) { + str = erealloc(str, (i+1) * sizeof(char)); + str[i] = buf; + i++; + } else { + str = erealloc(str, (i+1) * sizeof(char)); + str[i] = 0; + break; + } + } + fclose(fp); + return str; +} + +char * +file_extension_replace_or_add(const char *filepath, const char *extension) +{ + size_t extension_len = strlen(extension); + char *new = NULL; + int mark = -1; + int i, k; + int path_len; + for (i = 0; filepath[i]; i++) { + if (filepath[i] == '.') { + mark = i; + } + } + if (mark == -1) { + path_len = (int)strlen(filepath); + new = emalloc((path_len+2+extension_len) * sizeof(char)); + for (i = 0; i < path_len; i++) { + new[i] = filepath[i]; + } + new[i] = '.'; + i++; + for (k = 0; extension[k]; k++, i++) { + new[i] = extension[k]; + } + new[i] = 0; + } else { + new = emalloc((mark+2+extension_len) * sizeof(char)); + for (i = 0; i <= mark; i++) { + new[i] = filepath[i]; + } + for (k = 0; extension[k]; k++, i++) { + new[i] = extension[k]; + } + new[i] = 0; + } + return new; +} + +bool +file_extension_equals(const char *filepath, const char *extension) +{ + int mark = -1; + int i; + for (i = strlen(filepath)-1; i >= 0; i--) { + if (filepath[i] == '.') { + mark = i; + break; + } + } + if (!strcmp(&filepath[mark+1], extension)) { + return true; + } + return false; +} + +char * +filepath_add_ending_slash_if_missing(const char *path) +{ + size_t len = strlen(path); + if (path[len-1] == '/') { + return strdup(path); + } else { + char *path_with_slash = emalloc((len+2) * sizeof(char)); + strcpy(path_with_slash, path); + path_with_slash[len] = '/'; + path_with_slash[len+1] = 0; + return path_with_slash; + } +} + +char * +filepath_basename(const char *path) +{ + int begin = 0; + int i; + for (i = 0; path[i]; i++) { + if (path[i] == '/') { + begin = i+1; + } + } + return strdup(&path[begin]); +} + +char * +filepath_dirname(const char *path) +{ + char *dirname; + int i, end = 0; + for (i = 0; path[i]; i++) { + if (path[i] == '/') { + end = i; + } + } + if (end == 0) { + dirname = emalloc(2 * sizeof(char)); + dirname[0] = '.'; + dirname[1] = 0; + return dirname; + } + dirname = emalloc((end+1)* sizeof(char)); + for (i = 0; i < end; i++) { + dirname[i] = path[i]; + } + dirname[i] = 0; + return dirname; +} + +char * +filepath_resolve_tilde(const char *path) +{ + char *home; + char *str = NULL; + if (*path == '~') { + home = getenv("HOME"); + if (!home) { + LOG_DEBUG("getenv failed."); + return NULL; + } + str = erealloc(str, (strlen(home)+strlen(path)) * sizeof(char)); + strcpy(str, home); + strcat(str, &path[1]); + return str; + } else { + return strdup(path); + } +} + +static const char * +size_type_to_string(enum SizeType type) +{ + switch (type) { + case ST_PERCENT: + return "%"; + case ST_EM: + return "em"; + case ST_EX: + return "ex"; + default: + return ""; + } +} + +struct Size * +size_create(const char *str) +{ + size_t len = strlen(str); + struct Size *size = emalloc(sizeof(struct Size)); + char *endptr; + double d; + d = strtod(str, &endptr); + if (str == endptr || errno == ERANGE) { + LOG_DEBUG("strtod failed."); + return NULL; + } + size->d = d; + size->type = ST_POINT; + if (len > 1 && str[len-1] == '%') { + if (size->d < 1.0 || size->d > 100.0) { + util_log(LOG_ERR, "invalid percentage."); + return NULL; + } + size->d = d / 100.0; + size->type = ST_PERCENT; + } else + if (len > 2 && str[len-2] == 'e' && str[len-1] == 'm') { + size->type = ST_EM; + } else + if (len > 2 && str[len-2] == 'e' && str[len-1] == 'x') { + size->type = ST_EX; + } + return size; +} + +struct Size * +size_copy(struct Size *size) +{ + struct Size *copy = emalloc(sizeof(struct Size)); + copy->type = size->type; + copy->d = size->d; + return copy; +} + +const char * +size_to_string(struct Size *size) +{ + static char str[10+1]; + if (size->d > 999999) { + sprintf((char *)&str, ">999.999"); + } else { + sprintf((char *)&str, "%.1f%s", size->d, size_type_to_string(size->type)); + } + return str; +} diff --git a/src/util.h b/src/util.h @@ -0,0 +1,59 @@ +#include "types.h" + +#ifdef DEBUG +#define LOG_DEBUG(msg) fprintf(stderr, msg"\n") +#else +#define LOG_DEBUG(msg) +#endif + +#define LENGTH(x) (sizeof x / sizeof x[0]) + +#define COLOR_BOLD_RED "\033[1;31m" +#define COLOR_BOLD_ORANGE "\033[1;33m" +#define COLOR_BOLD_WHITE "\033[1;37m" +#define COLOR_BOLD_BLUE "\033[1;34m" +#define COLOR_RESET "\033[0m" + +enum LogLevel { + LOG_INFO, + LOG_WARN, + LOG_ERR, + LOG_TODO +}; + +enum FileType { + F_ERROR, + F_FOLDER, + F_REG_FILE, + F_OTHER +}; + +void util_log_enable_info_logs(void); + +void *emalloc(size_t size); +void *erealloc(void *ptr, size_t size); +void util_log(enum LogLevel level, const char *msg, ...); + +bool str_starts_with(const char *str, const char *part); +char *str_normalize(const char *str); +char *str_trim(const char *str); +char *str_remove_leading_whitespace(const char *str); +void strs_free(char **strs); +bool strs_has(char **strs, const char *str); +void strs_add(char ***strs, const char *str); +int str_compare(const char *a, const char *b); +long str_to_number(const char *str); + +enum FileType file_type(const char *path); +char *file_read(const char *filepath); +char *file_extension_replace_or_add(const char *filepath, const char *extension); +bool file_extension_equals(const char *filepath, const char *extension); + +char *filepath_add_ending_slash_if_missing(const char *path); +char *filepath_basename(const char *path); +char *filepath_dirname(const char *path); +char *filepath_resolve_tilde(const char *path); + +struct Size *size_create(const char *str); +struct Size *size_copy(struct Size *size); +const char *size_to_string(struct Size *size);