commit e79a2f3368a2880914ebb46945df5375a83ffbc3
parent afe2313c5a3b621d17ab59f875f317c88445d7d2
Author: nibo <nibo@relim.de>
Date: Sat, 8 Feb 2025 18:42:58 +0100
Add src/* files
Diffstat:
| A | src/chord_diagram.c | | | 740 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/chord_diagram.h | | | 26 | ++++++++++++++++++++++++++ |
| A | src/chordpro.c | | | 5470 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/chordpro.h | | | 175 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/config.c | | | 1065 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/config.h | | | 17 | +++++++++++++++++ |
| A | src/diagrams.h | | | 4394 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/lorid.c | | | 117 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/out_pdf.c | | | 2817 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/out_pdf.h | | | 103 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/types.h | | | 338 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/util.c | | | 502 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/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 *)¬es_german;
+ break;
+ case NS_SCANDINAVIAN:
+ notes = (struct Note *)¬es_scandinavian;
+ break;
+ case NS_LATIN:
+ notes = (struct Note *)¬es_latin;
+ break;
+ case NS_ROMAN:
+ notes = (struct Note *)¬es_roman;
+ break;
+ case NS_NASHVILLE:
+ notes = (struct Note *)¬es_nashville;
+ break;
+ default:
+ notes = (struct Note *)¬es_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 *)¬es_common;
+ break;
+ case NS_GERMAN:
+ notes = (struct Note *)¬es_german;
+ break;
+ case NS_SCANDINAVIAN:
+ notes = (struct Note *)¬es_scandinavian;
+ break;
+ case NS_LATIN:
+ notes = (struct Note *)¬es_latin;
+ break;
+ case NS_ROMAN:
+ notes = (struct Note *)¬es_roman;
+ break;
+ case NS_NASHVILLE:
+ notes = (struct Note *)¬es_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);