/* * Gregorio is a program that translates gabc files to GregorioTeX. * This file contains functions that deal with lyrics and styles. * * Copyright (C) 2008-2021 The Gregorio Project (see CONTRIBUTORS.md) * * This file is part of Gregorio. * * Gregorio is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Gregorio is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Gregorio. If not, see . */ /** * @file * @brief This file contains almost all the gregorio_character manipulation things. * * @warning The code is very spaghetti, but I don't really think I can do it another way with the chosen representation of styles, and the fact that everything must be xml-compliant and tex-compliant... * So this file is basically very hard to maintain. Moreover, it has not been very well coded, so it is even harder to maintain... */ #include "config.h" #include #include #include #include #include #include #include #include #include "bool.h" #include "struct.h" #include "unicode.h" #include "characters.h" #include "messages.h" #include "support.h" #include "utf8strings.h" #include "vowel/vowel.h" #define VOWEL_FILE "gregorio-vowels.dat" static bool read_vowel_rules(char *const lang) { char *language = lang; rulefile_parse_status status = RFPS_NOT_FOUND; char **filenames, *filename, **p; const char *description; int tries; gregorio_kpse_find_or_else(filenames, VOWEL_FILE, return false); gregorio_vowel_tables_init(); /* only need to try twice; if it's not resolved by then, there is an alias * loop */ for (tries = 0; tries < 2; ++tries) { for (p = filenames; status != RFPS_FOUND && (filename = *p); ++p) { /* read and parse the file */ gregorio_messagef("read_rules", VERBOSITY_INFO, 0, _("Looking for %s in %s"), language, filename); gregorio_vowel_tables_load(filename, &language, &status); switch (status) { case RFPS_FOUND: description = "Found"; break; case RFPS_ALIASED: description = "Aliased to"; break; default: description = "Could not find"; break; } gregorio_messagef("read_rules", VERBOSITY_INFO, 0, _("%s %s in %s"), description, language, filename); } if (status != RFPS_ALIASED) { /* if it's not aliased, there's no reason to scan again */ break; } } if ((strcmp(language, "Latin") == 0 || strcmp(language, "latin") == 0 || strcmp(language, "la") == 0 || strcmp(language, "lat") == 0) && status == RFPS_NOT_FOUND) { gregorio_messagef("read_rules", VERBOSITY_INFO, 0, "Falling back on internal Latin vowel rules"); } if (status == RFPS_ALIASED) { gregorio_messagef("read_rules", VERBOSITY_WARNING, 0, _("Unable to resolve alias for %s"), lang); } /* free the allocated memory */ for (p = filenames; *p; ++p) { free(*p); } free(filenames); if (language != lang) { free(language); } return status == RFPS_FOUND; } void gregorio_set_centering_language(char *const language) { if (!read_vowel_rules(language)) { if (strcmp(language, "Latin") != 0 && strcmp(language, "latin") != 0 && strcmp(language, "la") != 0 && strcmp(language, "lat") != 0) { gregorio_messagef("gregorio_set_centering_language", VERBOSITY_WARNING, 0, _("unable to read vowel files for " "%s; defaulting to Latin vowel rules"), language); } gregorio_vowel_tables_init(); gregorio_vowel_table_add(DEFAULT_VOWELS); gregorio_prefix_table_add("i"); gregorio_prefix_table_add("I"); gregorio_prefix_table_add("u"); gregorio_prefix_table_add("U"); } } static __inline const gregorio_character *skip_verbatim_or_special( const gregorio_character *ch) { /* skip past verbatim and special characters */ if (ch->cos.s.type == ST_T_BEGIN && (ch->cos.s.style == ST_VERBATIM || ch->cos.s.style == ST_SPECIAL_CHAR)) { if (ch->next_character) { ch = ch->next_character; } while (ch->next_character && ch->is_character) { ch = ch->next_character; } } return ch; } static __inline bool handle_elision(const gregorio_character *const ch, bool *in_elision) { if (ch->cos.s.style != ST_ELISION) { return false; } switch (ch->cos.s.type) { case ST_T_BEGIN: *in_elision = true; break; case ST_T_END: *in_elision = false; break; case ST_T_NOTHING: /* not reachable unless there's a programming error */ assert(false); /* LCOV_EXCL_LINE */ break; } return true; } static __inline int for_each_centerable_character( const gregorio_character *const character, void (*capture)(grewchar *, int, grewchar), grewchar *buffer) { int count; const gregorio_character *ch; bool in_elision = false; for (count = 0, ch = character; ch; ch = ch->next_character) { if (ch->is_character) { if (!in_elision) { if (capture) { capture(buffer, count, ch->cos.character); } ++count; } } else { if (ch->cos.s.style == ST_PROTRUSION_FACTOR) { /* if there is a protrusion, we will always encounter the * protrusion factor first */ break; } if (ch->cos.s.type == ST_T_BEGIN && ch->cos.s.style == ST_ELISION) { /* put in a special character at the beginning of an elision * that represents the elision */ if (capture) { capture(buffer, count, GREVOWEL_ELISION_MARK); } ++count; } if (!handle_elision(ch, &in_elision)) { ch = skip_verbatim_or_special(ch); } } } return count; } /* this function assumes subject is sized correctly */ static void capture_subject(grewchar *const subject, const int count, const grewchar character) { subject[count] = character; } static void determine_center(gregorio_character *character, int *start, int *end) { const int count = for_each_centerable_character(character, NULL, NULL); grewchar *subject; if (count == 0) { return; } subject = (grewchar *)gregorio_malloc((count + 1) * sizeof(grewchar)); for_each_centerable_character(character, capture_subject, subject); subject[count] = (grewchar)0; *start = *end = -1; gregorio_find_vowel_group(subject, start, end); free(subject); } static bool go_to_end_initial(gregorio_character **param_character) { bool has_initial = false; gregorio_character *current_character = *param_character; if (!current_character) { return false; } gregorio_go_to_first_character_c(¤t_character); /* skip past any initial */ if (!current_character->is_character && current_character->cos.s.type == ST_T_BEGIN && current_character->cos.s.style == ST_INITIAL) { has_initial = true; while (current_character) { if (!current_character->is_character && current_character->cos.s.type == ST_T_END && current_character->cos.s.style == ST_INITIAL) { break; } current_character = current_character->next_character; } } (*param_character) = current_character; return has_initial; } /* * The push function pushes a style in the stack, and updates first_style to * this element. */ static void style_push(det_style **current_style, grestyle_style style) { det_style *element; gregorio_assert(current_style, style_push, "current_style may not be NULL", return); element = (det_style *) gregorio_malloc(sizeof(det_style)); element->style = style; element->previous_style = NULL; element->next_style = (*current_style); if (*current_style) { (*current_style)->previous_style = element; } (*current_style) = element; } /* * Pop deletes an style in the stack (the style to delete is the parameter) */ static void style_pop(det_style **first_style, det_style *element) { gregorio_assert(element && first_style && *first_style, style_pop, "invalid arguments", return); if (element->previous_style) { assert(*first_style != element); element->previous_style->next_style = element->next_style; if (element->next_style) { element->next_style->previous_style = element->previous_style; } } else { if (element->next_style) { element->next_style->previous_style = NULL; (*first_style) = element->next_style; } else { (*first_style) = NULL; } } free(element); } /* * free_styles just frees the stack. */ static void free_styles(det_style **first_style) { det_style *current_style; gregorio_not_null(first_style, free_styles, return); current_style = (*first_style); while (current_style) { current_style = current_style->next_style; free((*first_style)); (*first_style) = current_style; } } /* * inline function that will be used for verbatim and special-characters in the * * next function, it calls function with a grewchar * which is the verbatim or * special-character. It places current_character to the character next to the * end of the verbatim or special_char charachters. */ static __inline void verb_or_sp(const gregorio_character **ptr_character, const grestyle_style style, FILE *const f, void (*const function) (FILE *, const grewchar *)) { int i, j; grewchar *text; const gregorio_character *current_character; const gregorio_character *begin_character; i = 0; j = 0; current_character = (*ptr_character)->next_character; begin_character = current_character; while (current_character) { if (current_character->cos.s.type == ST_T_END && current_character->cos.s.style == style) { break; } else { if (current_character->is_character) { i++; } current_character = current_character->next_character; } } if (i == 0) { *ptr_character = current_character; return; } text = (grewchar *) gregorio_malloc((i + 1) * sizeof(grewchar)); current_character = begin_character; while (j < i) { if (current_character->is_character) { text[j] = current_character->cos.character; j++; } current_character = current_character->next_character; } text[i] = 0; function(f, text); free(text); *ptr_character = current_character; } /** * This function is made to simplify the output modules : they just have to * declare some simple functions (what to do when meeting a beginning of style, * a character, etc.) and to call this function with pointer to these * functions, and that will automatically write the good ouput. This function * does not test at all the gregorio_character list, if it is wrong, then the * ouput will be wrong. It is very simple to understand, even if it is a bit * long. * * @warning The difficulty comes when we have to write the first syllable text, * without the first letter. * The behaviour can have some bugs in this case if the first syllable has some * complex styles. It would be a bit stupid to do such a thing, but users are * usually very creative when it comes to inventing twisted things... */ void gregorio_write_text(const gregorio_write_text_phase phase, const gregorio_character *current_character, FILE *const f, void (*const printverb) (FILE *, const grewchar *), void (*const printchar) (FILE *, grewchar), void (*const begin) (FILE *, grestyle_style), void (*const end) (FILE *, grestyle_style), void (*const printspchar) (FILE *, const grewchar *)) { gregorio_assert(current_character, gregorio_write_text, "current_character may not be NULL", return); while (current_character) { if (current_character->is_character) { printchar(f, current_character->cos.character); } else { if (current_character->cos.s.type == ST_T_BEGIN) { switch (current_character->cos.s.style) { case ST_VERBATIM: verb_or_sp(¤t_character, ST_VERBATIM, f, printverb); break; case ST_SPECIAL_CHAR: verb_or_sp(¤t_character, ST_SPECIAL_CHAR, f, printspchar); break; case ST_INITIAL: if (phase == WTP_FIRST_SYLLABLE) { while (current_character) { if (!current_character->is_character && current_character->cos.s.type == ST_T_END && current_character->cos.s.style == ST_INITIAL) { break; } current_character = current_character->next_character; } } else { begin(f, ST_INITIAL); } break; default: begin(f, current_character->cos.s.style); break; } } else { /* ST_T_END */ end(f, current_character->cos.s.style); } } if (current_character) { current_character = current_character->next_character; } } } void gregorio_write_first_letter_alignment_text( const gregorio_write_text_phase phase, const gregorio_character *current_character, FILE *const f, void (*const printverb) (FILE *, const grewchar *), void (*const printchar) (FILE *, grewchar), void (*const begin) (FILE *, grestyle_style), void (*const end) (FILE *, grestyle_style), void (*const printspchar) (FILE *, const grewchar *)) { /* stack of styles to close and reopen */ det_style *first_style = NULL; det_style *last_style = NULL; int first_letter_open = (phase == WTP_FIRST_SYLLABLE)? 2 : 1; gregorio_assert(current_character, gregorio_write_first_letter_alignment_text, "current_character may not be NULL", return); /* go to the first character */ gregorio_go_to_first_character(¤t_character); if (phase != WTP_FIRST_SYLLABLE) { begin(f, ST_SYLLABLE_INITIAL); } /* loop until there are no characters left */ for (; current_character; current_character = current_character->next_character) { bool close_first_letter = false; /* found a real character */ if (current_character->is_character) { printchar(f, current_character->cos.character); close_first_letter = first_letter_open != 0; } else switch (current_character->cos.s.type) { case ST_T_NOTHING: /* not reachable unless there's a programming error */ assert(false); /* LCOV_EXCL_LINE */ break; case ST_T_BEGIN: /* handle styles */ switch (current_character->cos.s.style) { case ST_CENTER: case ST_FORCED_CENTER: case ST_INITIAL: case ST_FIRST_SYLLABLE_INITIAL: /* ignore */ break; case ST_VERBATIM: verb_or_sp(¤t_character, ST_VERBATIM, f, printverb); close_first_letter = first_letter_open != 0; break; case ST_SPECIAL_CHAR: verb_or_sp(¤t_character, ST_SPECIAL_CHAR, f, printspchar); close_first_letter = first_letter_open != 0; break; case ST_FIRST_WORD: case ST_FIRST_SYLLABLE: if (phase == WTP_FIRST_SYLLABLE) { break; } /* else fall through */ default: /* push the style onto the stack */ style_push(&first_style, current_character->cos.s.style); begin(f, current_character->cos.s.style); } break; case ST_T_END: switch (current_character->cos.s.style) { case ST_CENTER: case ST_FORCED_CENTER: case ST_FIRST_SYLLABLE_INITIAL: /* ignore */ break; case ST_INITIAL: /* in the case where the initial is closed before the first * letter */ close_first_letter = first_letter_open > 1; break; case ST_VERBATIM: case ST_SPECIAL_CHAR: /* not reachable unless there's a programming error */ assert(false); /* LCOV_EXCL_LINE */ break; case ST_FIRST_WORD: case ST_FIRST_SYLLABLE: if (phase == WTP_FIRST_SYLLABLE) { break; } /* else fall through */ default: /* pop the style from the stack */ assert(first_style); assert(first_style->style == current_character->cos.s.style); style_pop(&first_style, first_style); end(f, current_character->cos.s.style); } break; } while (close_first_letter || (!current_character->next_character && first_letter_open > 0)) { close_first_letter = false; --first_letter_open; /* close all the styles in the stack */ if (first_style) { det_style *current_style; /* these are immediately closed, so go past them */ while (first_style && current_character && current_character->next_character && !current_character->next_character->is_character && current_character->next_character->cos.s.type == ST_T_END && current_character->next_character->cos.s.style == first_style->style) { current_character = current_character->next_character; style_pop(&first_style, first_style); end(f, current_character->cos.s.style); } for (current_style = first_style; current_style; current_style = current_style->next_style) { end(f, current_style->style); last_style = current_style; } } end(f, ST_SYLLABLE_INITIAL); /* reopen all styles */ for (; last_style; last_style = last_style->previous_style) { begin(f, last_style->style); } } } if (phase == WTP_FIRST_SYLLABLE) { /* the only way this can happen is if the first syllable is empty, * but if the first syllable is empty, this function will not be * called */ gregorio_assert_only(!first_letter_open, gregorio_write_first_letter_alignment_text, "first_syllable was not closed properly"); } free_styles(&first_style); } /* * Here starts the code of the handling of text and styles. * * This part is not the easiest, in fact is is the most complicated. The reason * is that I want to make something coherent in memory (easy to interprete), * and to let the user type whatever he wants. This part was originally written * for gabc, that's why it's always talking about it. But it could as well be * used by other read plugins, to put things in order. * * Basically all the following lines of code are made for the last function, * that will take a "user-written" gregorio_character list into a xml-compliant * and tex-compliant list. It's more complicated than it seems... * * Functionalities: For example if the user types tttttttttt I want * it to be represented as tttttttttt. The fabulous thing * is that it is xml-compliant. This part also determines the middle, for * example pot will be interpreted as p{o}t. When I did that I also thought * about TeX styles that needed things like {p}{o}{t}, so when the user types * pot, it is interpreted as p{o}t. * * Internal structure: To do so we have a structure, det_style, that will help * us : it is a stack (double chained list) of the styles that we have seen * until now. When we encounter a , we push the i style on the stack. If we * encounter a we suppress the i style from the stack. Let's take a more * complex example: if we encounter , the stack will be first null, * then i then bi, and there we want to end i, but it is not the first style of * the stack, so we close all the styles that we encounter before we encounter * i (remember, this is for xml-compliance), so we insert a before the * . But that's not all, we also write a after the , so that the b * style continues. There our stack is just b. For center, we just close all * the styles in the stack, insert a { and reopen all the styles in the stack. * * The structure used for character, styles, etc. is described in * include/struct.h * * The functionment in this file is quite simple : we add all the characters * that we see, even if they are incoherent, in the gregorio_character list, * and then we call a very complex function that will build a stack of the * style, determine the middle, make all xml-compliant, mow the lawn, etc. * * This code is *really* spaghetti, but I think it's a necessary pain. * */ /* * * This function inserts a style before current_character, updating the double * chained list. * */ static void insert_style_before(unsigned char type, unsigned char style, gregorio_character *current_character) { gregorio_character *element = (gregorio_character *) gregorio_malloc(sizeof(gregorio_character)); element->is_character = 0; element->cos.s.type = type; element->cos.s.style = style; element->next_character = current_character; if (current_character->previous_character) { current_character->previous_character->next_character = element; } element->previous_character = current_character->previous_character; current_character->previous_character = element; } /* * * This function puts a style after current_character, and updates * current_character to the gregorio_character it created. It updates the * double chained list. It does not touche to the det_styles list. * */ static void insert_style_after(unsigned char type, unsigned char style, gregorio_character **current_character) { gregorio_character *element = (gregorio_character *) gregorio_malloc(sizeof(gregorio_character)); element->is_character = 0; element->cos.s.type = type; element->cos.s.style = style; element->next_character = (*current_character)->next_character; if ((*current_character)->next_character) { (*current_character)->next_character->previous_character = element; } element->previous_character = (*current_character); (*current_character)->next_character = element; (*current_character) = element; } #if 0 static void insert_char_after(grewchar c, gregorio_character **current_character) { gregorio_character *element = (gregorio_character *) gregorio_malloc(sizeof(gregorio_character)); element->is_character = 1; element->cos.character = c; element->next_character = (*current_character)->next_character; if ((*current_character)->next_character) { (*current_character)->next_character->previous_character = element; } element->previous_character = (*current_character); (*current_character)->next_character = element; (*current_character) = element; } #endif /* * * This function suppresses the character, updates the double chained * list, and returns the character after, if there is one. * */ static __inline gregorio_character *suppress_character( gregorio_character *current_character) { if (current_character) { gregorio_character *thischaracter = current_character; if (current_character->previous_character) { current_character->previous_character->next_character = current_character->next_character; } if (current_character->next_character) { current_character->next_character->previous_character = current_character->previous_character; } current_character = current_character->next_character; free(thischaracter); } return current_character; } /* * * Then start the inline functions for the big function. the first one is the * function we call when we close a style. The magic thing is that is will * prevent things like a{b: when the user types ab, if the * middle is between a and b (...), it will interpret it as a{b. * */ static __inline void close_style(gregorio_character *current_character, det_style *current_style) { /* we suppose that there is a previous_character, because there is a * current_style */ gregorio_assert(current_character && current_character->previous_character, close_syllable, "have a style but no previous character", return); if (!current_character->previous_character->is_character && current_character->previous_character->cos.s.style == current_style->style) { /* we do it this way rather than calling suppress_character on the * previous character because the intent is more obvious to both the * human reading it and to the clang static analyzer */ gregorio_character *tofree = current_character->previous_character; if (tofree->previous_character) { tofree->previous_character->next_character = current_character; } current_character->previous_character = tofree->previous_character; free(tofree); } else { insert_style_before(ST_T_END, current_style->style, current_character); } } /* * * next the function called when we have determined that we must end the center * here : it closes all styles, adds a } and then reopens all styles. * */ /* type is ST_CENTER or ST_FORCED_CENTER */ static __inline void end_center(grestyle_style type, gregorio_character *current_character, det_style **ptr_style) { det_style *current_style; current_style = *ptr_style; while (current_style) { close_style(current_character, current_style); if (current_style->next_style) { current_style = current_style->next_style; } else { break; } } insert_style_before(ST_T_END, type, current_character); while (current_style) { insert_style_before(ST_T_BEGIN, current_style->style, current_character); if (current_style->previous_style) { current_style = current_style->previous_style; } else { break; } } *ptr_style = current_style; } /* * about the same, but adding a { */ static __inline void begin_center(grestyle_style type, gregorio_character *current_character, det_style **ptr_style) { det_style *current_style; current_style = *ptr_style; while (current_style) { close_style(current_character, current_style); if (current_style->next_style) { current_style = current_style->next_style; } else { break; } } insert_style_before(ST_T_BEGIN, type, current_character); while (current_style) { insert_style_before(ST_T_BEGIN, current_style->style, current_character); if (current_style->previous_style) { current_style = current_style->previous_style; } else { break; } } *ptr_style = current_style; } /* * THE big function. Very long, using a lot of long macros, etc. I hope you * really want to understand it, 'cause it won't be easy. * * current character is a pointer to a gregorio_character. The * gregorio_character double-chained list it is in will be totally reorganized * so that it is xml compliant, and the function will update it to the first * character of this brand new list. * * center_is_determined has the values present in characters.h. * * Another difficulty is the fact that we must consider characters in verbatim * and special character styles like only one block, we can't say the center is * in the middle of a verbatim block. */ void gregorio_rebuild_characters(gregorio_character **const param_character, gregorio_center_determination center_is_determined, bool skip_initial) { /* the current_character */ gregorio_character *current_character = *param_character; gregorio_character *sentinel; det_style *first_style = NULL; /* determining the type of centering (forced or not) */ grestyle_style center_type = ST_NO_STYLE; int start = -1, end = -1, index = -1; bool in_elision = false; det_style *current_style; /* so, here we start: we go to the first_character */ if (skip_initial && go_to_end_initial(¤t_character)) { if (!current_character->next_character) { /* nothing else to rebuild, but the initial needs to be ST_CENTER */ insert_style_after(ST_T_END, ST_CENTER, ¤t_character); gregorio_go_to_first_character_c(¤t_character); insert_style_before(ST_T_BEGIN, ST_CENTER, current_character); gregorio_go_to_first_character_c(¤t_character); (*param_character) = current_character; return; } /* move to the character after the initial */ current_character = current_character->next_character; } else { if (current_character) { gregorio_go_to_first_character_c(¤t_character); } } /* first we see if there is already a center determined */ if (center_is_determined == 0) { center_type = ST_CENTER; determine_center(current_character, &start, &end); } else { center_type = ST_FORCED_CENTER; } if (!current_character) { return; } for (sentinel = current_character; sentinel->next_character; sentinel = sentinel->next_character) { /* just loop to get the last character */ } /* put a sentinel at the end to aid in looping */ insert_style_after(ST_T_NOTHING, ST_SENTINEL, &sentinel); /* we loop until there isn't any character */ while (current_character && current_character != sentinel) { /* IMPORTANT: A continue inside this loop MUST put current_character on * the next character to process */ /* the first part of the function deals with real characters (not * styles) */ if (current_character->is_character) { if (!in_elision) { ++index; if (!center_is_determined && index == start) { /* the firstcase is if the user hasn't determined the * middle, and we have only seen vowels so far (else * center_is_determined would be DETERMINING_MIDDLE). The * current_character is the first vowel, so we start the * center here. */ begin_center(center_type, current_character, &first_style); center_is_determined = CENTER_DETERMINING_MIDDLE; } else if (center_is_determined == CENTER_DETERMINING_MIDDLE && index == end) { /* the case where the user has not determined the middle * and we are in the middle section of the syllable, but * there we encounter something that is not a vowel, so the * center ends there. */ end_center(center_type, current_character, &first_style); center_is_determined = CENTER_FULLY_DETERMINED; } /* else it is just a normal character... we simply pass. */ } } else { const grestyle_style style = current_character->cos.s.style; /* there starts the second part of the function that deals with the * styles characters */ switch (current_character->cos.s.type) { case ST_T_BEGIN: /* the beginning of a style */ if (center_is_determined == CENTER_DETERMINING_MIDDLE && index == end - 1) { /* if we are determining the end of the center and we have * a, ELISION, VERBATIM or SPECIAL_CHAR style, we end the * center determination */ switch (style) { case ST_VERBATIM: case ST_SPECIAL_CHAR: case ST_ELISION: end_center(center_type, current_character, &first_style); center_is_determined = CENTER_FULLY_DETERMINED; break; default: /* something else; don't do anything */ break; } } if (style == ST_ELISION) { /* skip over the elision marker in the vowel determination * buffer */ ++index; } switch (style) { case ST_VERBATIM: case ST_SPECIAL_CHAR: /* Here we pass all the characters after a verbatim (or * special char) beginning, until we find a style (begin or * end) */ if (current_character->next_character) { current_character = current_character->next_character; } while (current_character && current_character != sentinel && current_character->is_character) { current_character = current_character->next_character; } assert(current_character); if (current_character != sentinel) { gregorio_assert2(!current_character->is_character && current_character->cos.s.type == ST_T_END && current_character->cos.s.style == style, gregorio_rebuild_characters, "mismatched %s", grestyle_style_to_string(style), break); } break; case ST_CENTER: /* not reachable unless there's a programming error */ /* LCOV_EXCL_START */ gregorio_fail(gregorio_rebuild_characters, "encountered unexpected begin ST_CENTER"); break; /* LCOV_EXCL_STOP */ case ST_FORCED_CENTER: begin_center(center_type, current_character, &first_style); current_character = suppress_character(current_character); continue; default: handle_elision(current_character, &in_elision); /* it the beginning of a style, which is not center. We * check if the style is not already in the stack. If it is * the case, we suppress the character and pass */ for (current_style = first_style; current_style && current_style->style != current_character->cos.s.style; current_style = current_style->next_style) { /* just loop */ } if (current_style) { /* repeated styles should already have been filtered out * during parsing, so if this is hit, it's a programming * error */ /* LCOV_EXCL_START */ gregorio_fail(gregorio_rebuild_characters, "encountered repeated style"); current_character = suppress_character(current_character); continue; /* LCOV_EXCL_STOP */ } /* if it is something to add then we just push the style in the * stack and continue. */ style_push(&first_style, current_character->cos.s.style); break; } break; case ST_T_END: /* the beginning of a style */ switch(current_character->cos.s.style) { case ST_VERBATIM: case ST_SPECIAL_CHAR: case ST_CENTER: /* not reachable unless there's a programming error */ /* LCOV_EXCL_START */ gregorio_fail2(gregorio_rebuild_characters, "encountered unexpected end %s", grestyle_style_to_string( current_character->cos.s.style)); break; /* LCOV_EXCL_STOP */ case ST_FORCED_CENTER: end_center(center_type, current_character, &first_style); current_character = suppress_character(current_character); continue; default: handle_elision(current_character, &in_elision); /* the case of the end of a style (the most complex). * First, we have to see if the style is in the stack. If * there is no stack, we just suppress and continue. */ if (!first_style) { current_character = suppress_character(current_character); continue; } /* so, we look if it is in the stack. If it is we put * current_style to the style just before the style * corresponding to the character that we are treating * (still there ?) */ for (current_style = first_style; current_style && current_style->style != current_character->cos.s.style; current_style = current_style->next_style) { /* just loop */ } if (current_style) { det_style *this_style; /* if there are styles before in the stack, we close * them */ for (this_style = first_style; this_style != current_style; this_style = this_style->next_style) { insert_style_before(ST_T_END, this_style->style, current_character); } /* and then we reopen them */ for (this_style = this_style->previous_style; this_style; this_style = this_style->previous_style) { insert_style_after(ST_T_BEGIN, this_style->style, ¤t_character); } /* we delete the style in the stack */ style_pop(&first_style, current_style); } else { current_character = suppress_character(current_character); continue; } } break; default: /* not reachable unless there's a programming error */ /* LCOV_EXCL_START */ gregorio_fail(gregorio_rebuild_characters, "unexpected style type"); } /* LCOV_EXCL_STOP */ } current_character = current_character->next_character; } /* destroy the sentinel */ current_character = sentinel->previous_character; current_character->next_character = NULL; free(sentinel); /* we terminate all the styles that are still in the stack */ for (current_style = first_style; current_style; current_style = current_style->next_style) { insert_style_after(ST_T_END, current_style->style, ¤t_character); } /* current_character is at the end of the list now, so if we havn't closed * the center, we do it at the end. */ if (center_is_determined != CENTER_FULLY_DETERMINED) { insert_style_after(ST_T_END, center_type, ¤t_character); } /* these three lines are for the case where the user didn't tell anything * about the middle and there aren't any vowel in the syllable, so we * begin the center before the first character (you can notice that there * is no problem of style). */ if (!center_is_determined) { if (skip_initial && go_to_end_initial(¤t_character)) { current_character = current_character->next_character; } else { gregorio_go_to_first_character_c(¤t_character); } insert_style_before(ST_T_BEGIN, ST_CENTER, current_character); } /* well.. you're quite brave if you reach this comment. */ gregorio_go_to_first_character_c(¤t_character); (*param_character) = current_character; free_styles(&first_style); } /** * @brief This function will determine the behaviour of gregorio when it comes to the recognition of the initial. * * Basically it will take a gregorio_character list and return the same list, but with a style added : ST_INITIAL. This style will incidate the initial. The center will be placed at the second letter, unless there is a FORCED_CENTER somewhere. * Finally all will be xml-compliant and tex-compliant when we call gregorio_rebuild_characters. * * If we note <> for the initial and {} for the center, here is what we want: * * @li \verbatim Po ->

{o} \endverbatim * @li \verbatim {A}b -> <>{A}b \endverbatim * @li \verbatim {}a -> <>{}a \endverbatim * @li \verbatim Glo -> {l}o \endverbatim * @li \verbatim Gl{o} -> l{o} \endverbatim * * @param param_character is a pointer to the (pointer to the) first character, it will be modified so that it points to the new first character. */ void gregorio_rebuild_first_syllable(gregorio_character **param_character, bool separate_initial) { /* the current_character */ gregorio_character *current_character = *param_character; gregorio_character *first_character; gregorio_character *start_of_special; /* so, here we start: we go to the first_character */ gregorio_go_to_first_character_c(¤t_character); /* first we look at the styles to see if there is a FORCED_CENTER * somewhere */ /* Note: we don't have to deal with ST_CENTER here because * gregorio_rebuild_first_syllable is meant to be run **before** * gregorio_rebuild_characters, which is what adds ST_CENTER */ gregorio_assert(param_character, gregorio_rebuild_first_syllable, "param_character may not be NULL", return); while (current_character) { if (!current_character->is_character) { if (current_character->cos.s.style == ST_FORCED_CENTER) { /* we can break here, as there won't be forced center plus * center */ break; } } current_character = current_character->next_character; } current_character = *param_character; gregorio_go_to_first_character_c(¤t_character); first_character = current_character; /* now we are going to place the two INITIAL styles (begin and end) */ while (current_character) { if (!current_character->is_character && (current_character->cos.s.style == ST_FORCED_CENTER || current_character->cos.s.style == ST_PROTRUSION_FACTOR)) { /* we don't touch anything after a FORCED_CENTER, so we put an * empty INITIAL style just before; we handle the weird case of * a protrusion (manual or automatic) at the initial the same way * (we need only check for PROTRUSION_FACTOR since that will come * before PROTRUSION) */ current_character = first_character; insert_style_before(ST_T_BEGIN, ST_INITIAL, current_character); current_character = current_character->previous_character; insert_style_after(ST_T_END, ST_INITIAL, ¤t_character); break; } /* this if is a hack to make gregorio consider verbatim blocks and * special chars like one block, not a sequence of letters */ if (!current_character->is_character && current_character->cos.s.type == ST_T_BEGIN && (current_character->cos.s.style == ST_VERBATIM || current_character->cos.s.style == ST_SPECIAL_CHAR)) { insert_style_before(ST_T_BEGIN, ST_INITIAL, current_character); start_of_special = current_character->previous_character; /* ... which is now the begin-initial style */ if (current_character->next_character) { current_character = current_character->next_character; } while (current_character->next_character && current_character->is_character) { current_character = current_character->next_character; } insert_style_after(ST_T_END, ST_INITIAL, ¤t_character); if (separate_initial && start_of_special->previous_character) { /* we need to move the initial to the front */ start_of_special->previous_character->next_character = current_character->next_character; current_character->next_character->previous_character = start_of_special->previous_character; start_of_special->previous_character = NULL; current_character->next_character = first_character; first_character->previous_character = current_character; } break; } if (current_character->is_character) { if (separate_initial && current_character->previous_character) { /* we need to move the initial to the front */ current_character->previous_character->next_character = current_character->next_character; current_character->next_character->previous_character = current_character->previous_character; current_character->previous_character = NULL; current_character->next_character = first_character; first_character->previous_character = current_character; } insert_style_before(ST_T_BEGIN, ST_INITIAL, current_character); insert_style_after(ST_T_END, ST_INITIAL, ¤t_character); break; } current_character = current_character->next_character; } /* now apply the first syllable style */ current_character = *param_character; if (separate_initial) { if (go_to_end_initial(¤t_character)) { current_character = current_character->next_character; } } else { gregorio_go_to_first_character_c(¤t_character); } if (current_character) { bool marked_syllable_initial = false; insert_style_before(ST_T_BEGIN, ST_FIRST_SYLLABLE, current_character); do { if (!marked_syllable_initial && (current_character->is_character || (current_character->cos.s.type == ST_T_BEGIN && (current_character->cos.s.style == ST_VERBATIM || current_character->cos.s.style == ST_SPECIAL_CHAR ) ) )) { marked_syllable_initial = true; insert_style_before(ST_T_BEGIN, ST_FIRST_SYLLABLE_INITIAL, current_character); if (!current_character->is_character) { /* skip past the verbatim or special character */ if (current_character->next_character) { current_character = current_character->next_character; } while (current_character->next_character && current_character->is_character) { current_character = current_character->next_character; } } insert_style_after(ST_T_END, ST_FIRST_SYLLABLE_INITIAL, ¤t_character); } if (!current_character->is_character) { switch (current_character->cos.s.style) { case ST_CENTER: case ST_FORCED_CENTER: insert_style_before(ST_T_END, ST_FIRST_SYLLABLE, current_character); insert_style_after(ST_T_BEGIN, ST_FIRST_SYLLABLE, ¤t_character); break; case ST_PROTRUSION_FACTOR: /* protrusion is not allowed to be the syllable initial; * we only need to check PROTRUSION_FACTOR since that will * come before PROTRUSION */ marked_syllable_initial = true; break; default: break; } } if (!current_character->next_character) { insert_style_after(ST_T_END, ST_FIRST_SYLLABLE, ¤t_character); } current_character = current_character->next_character; } while (current_character); } current_character = *param_character; gregorio_go_to_first_character_c(¤t_character); (*param_character) = current_character; } void gregorio_set_first_word(gregorio_character **const character) { gregorio_character *ch = *character; if (go_to_end_initial(&ch)) { ch = ch->next_character; } if (ch) { bool started_style = false; while (ch) { if (!ch->is_character && (ch->cos.s.style == ST_CENTER || ch->cos.s.style == ST_FORCED_CENTER)) { if (started_style) { started_style = false; insert_style_before(ST_T_END, ST_FIRST_WORD, ch); } } else if (!started_style) { started_style = true; insert_style_before(ST_T_BEGIN, ST_FIRST_WORD, ch); } if (!ch->next_character && started_style) { insert_style_after(ST_T_END, ST_FIRST_WORD, &ch); } ch = ch->next_character; } } /* else there are no more characters here */ if (*character) { gregorio_go_to_first_character_c(character); } }