summaryrefslogtreecommitdiff
path: root/Build/source/texk/gregorio/gregorio-src/src/gregoriotex
diff options
context:
space:
mode:
authorKarl Berry <karl@freefriends.org>2021-02-25 19:22:25 +0000
committerKarl Berry <karl@freefriends.org>2021-02-25 19:22:25 +0000
commitad547a6b5986815fda458221149728d9d9ab1d87 (patch)
tree16296910eb3eca724371474ea9aea3994dc69614 /Build/source/texk/gregorio/gregorio-src/src/gregoriotex
parent947b43de3dd21d58ccc2ffadefc4441ea1c2a813 (diff)
restore Build,TODO from r57911
git-svn-id: svn://tug.org/texlive/trunk@57915 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Build/source/texk/gregorio/gregorio-src/src/gregoriotex')
-rw-r--r--Build/source/texk/gregorio/gregorio-src/src/gregoriotex/gregoriotex-position.c1750
-rw-r--r--Build/source/texk/gregorio/gregorio-src/src/gregoriotex/gregoriotex-write.c4539
-rw-r--r--Build/source/texk/gregorio/gregorio-src/src/gregoriotex/gregoriotex.h151
3 files changed, 6440 insertions, 0 deletions
diff --git a/Build/source/texk/gregorio/gregorio-src/src/gregoriotex/gregoriotex-position.c b/Build/source/texk/gregorio/gregorio-src/src/gregoriotex/gregoriotex-position.c
new file mode 100644
index 00000000000..dd88699392b
--- /dev/null
+++ b/Build/source/texk/gregorio/gregorio-src/src/gregoriotex/gregoriotex-position.c
@@ -0,0 +1,1750 @@
+/*
+ * Gregorio is a program that translates gabc files to GregorioTeX
+ * This file contains the logic for positioning signs on neumes.
+ *
+ * Copyright (C) 2008-2019 The Gregorio Project (see CONTRIBUTORS.md)
+ *
+ * This file is part of Gregorio.
+ *
+ * This program 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.
+ *
+ * This program 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
+ * this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "config.h"
+#include <stdlib.h>
+#include <assert.h>
+#include "bool.h"
+#include "struct.h"
+
+#include "gregoriotex.h"
+
+/* (loose) naming convention, employing camel case to be TeX-csname-compliant:
+ * {specific-glyph-shape}{note-position}{note-shape}{first-ambitus}{second-ambitus}
+ */
+OFFSET_CASE(FinalPunctum);
+OFFSET_CASE(FinalDeminutus);
+OFFSET_CASE(PenultBeforePunctumWide);
+OFFSET_CASE(PenultBeforeDeminutus);
+OFFSET_CASE(AntepenultBeforePunctum);
+OFFSET_CASE(AntepenultBeforeDeminutus);
+OFFSET_CASE(InitialPunctum);
+OFFSET_CASE(InitioDebilis);
+OFFSET_CASE(PorrNonAuctusInitialWide);
+OFFSET_CASE(PorrNonAuctusInitialOne);
+OFFSET_CASE(PorrAuctusInitialAny);
+OFFSET_CASE(FinalInclinatum);
+OFFSET_CASE(FinalInclinatumDeminutus);
+OFFSET_CASE(FinalStropha);
+OFFSET_CASE(FinalQuilisma);
+OFFSET_CASE(FinalOriscus);
+OFFSET_CASE(PenultBeforePunctumOne);
+OFFSET_CASE(FinalUpperPunctum);
+OFFSET_CASE(InitialOriscus);
+OFFSET_CASE(InitialQuilisma);
+OFFSET_CASE(TorcResNonAuctusSecondWideWide);
+OFFSET_CASE(TorcResNonAuctusSecondOneWide);
+OFFSET_CASE(TorcResDebilisNonAuctusSecondAnyWide);
+OFFSET_CASE(FinalLineaPunctum);
+OFFSET_CASE(TorcResQuilismaNonAuctusSecondWideWide);
+OFFSET_CASE(TorcResOriscusNonAuctusSecondWideWide);
+OFFSET_CASE(TorcResQuilismaNonAuctusSecondOneWide);
+OFFSET_CASE(TorcResOriscusNonAuctusSecondOneWide);
+OFFSET_CASE(TorcResNonAuctusSecondWideOne);
+OFFSET_CASE(TorcResDebilisNonAuctusSecondAnyOne);
+OFFSET_CASE(TorcResQuilismaNonAuctusSecondWideOne);
+OFFSET_CASE(TorcResOriscusNonAuctusSecondWideOne);
+OFFSET_CASE(TorcResNonAuctusSecondOneOne);
+OFFSET_CASE(TorcResQuilismaNonAuctusSecondOneOne);
+OFFSET_CASE(TorcResOriscusNonAuctusSecondOneOne);
+OFFSET_CASE(TorcResAuctusSecondWideAny);
+OFFSET_CASE(TorcResDebilisAuctusSecondAnyAny);
+OFFSET_CASE(TorcResQuilismaAuctusSecondWideAny);
+OFFSET_CASE(TorcResOriscusAuctusSecondWideAny);
+OFFSET_CASE(TorcResAuctusSecondOneAny);
+OFFSET_CASE(TorcResQuilismaAuctusSecondOneAny);
+OFFSET_CASE(TorcResOriscusAuctusSecondOneAny);
+OFFSET_CASE(ConnectedPenultBeforePunctumWide);
+OFFSET_CASE(ConnectedPenultBeforePunctumOne);
+OFFSET_CASE(InitialConnectedPunctum);
+OFFSET_CASE(InitialConnectedVirga);
+OFFSET_CASE(InitialConnectedQuilisma);
+OFFSET_CASE(InitialConnectedOriscus);
+OFFSET_CASE(FinalConnectedPunctum);
+OFFSET_CASE(FinalConnectedAuctus);
+OFFSET_CASE(FinalVirgaAuctus);
+OFFSET_CASE(FinalConnectedVirga);
+OFFSET_CASE(InitialVirga);
+OFFSET_CASE(SalicusOriscusWide);
+OFFSET_CASE(SalicusOriscusOne);
+OFFSET_CASE(LeadingPunctum);
+OFFSET_CASE(LeadingQuilisma);
+OFFSET_CASE(LeadingOriscus);
+OFFSET_CASE(Flat);
+OFFSET_CASE(Sharp);
+OFFSET_CASE(Natural);
+
+static __inline const char *note_before_last_note_case_ignoring_deminutus(
+ const gregorio_note *const current_note)
+{
+ assert(current_note->next);
+ if ((current_note->u.note.pitch - current_note->next->u.note.pitch) == 1
+ || (current_note->u.note.pitch -
+ current_note->next->u.note.pitch) == -1) {
+ if (!current_note->previous || current_note->u.note.pitch -
+ current_note->previous->u.note.pitch > 1) {
+ return ConnectedPenultBeforePunctumOne;
+ } else {
+ return PenultBeforePunctumOne;
+ }
+ } else {
+ if (!current_note->previous || current_note->u.note.pitch -
+ current_note->previous->u.note.pitch > 1) {
+ return ConnectedPenultBeforePunctumWide;
+ } else {
+ return PenultBeforePunctumWide;
+ }
+ }
+}
+
+static __inline const char *note_before_last_note_case(
+ const gregorio_glyph *const current_glyph,
+ const gregorio_note *const current_note)
+{
+ if ((current_glyph->u.notes.liquescentia & L_DEMINUTUS)
+ && current_note->next) {
+ return PenultBeforeDeminutus;
+ } else {
+ return note_before_last_note_case_ignoring_deminutus(current_note);
+ }
+}
+
+/* num can be FinalPunctum or FinalUpperPunctum according if the last note is a
+ * standard punctum or a smaller punctum (for pes, porrectus and torculus
+ * resupinus */
+static __inline const char *last_note_case(
+ const gregorio_glyph *const current_glyph, const char* offset_pos,
+ gregorio_note *current_note, bool no_ambitus_one)
+{
+ if (current_glyph->u.notes.liquescentia & L_DEMINUTUS) {
+ /*
+ * may seem strange, but it is unlogical to typeset a small horizontal
+ * episema at the end of a flexus deminutus
+ */
+ return FinalDeminutus;
+ }
+ if (!current_note->previous
+ || (!no_ambitus_one && (offset_pos == FinalUpperPunctum
+ || current_note->previous->u.note.pitch -
+ current_note->u.note.pitch == 1 ||
+ current_note->u.note.pitch - current_note->previous->u.note.pitch
+ == 1))) {
+ return offset_pos;
+ }
+ if (current_note->previous->u.note.pitch < current_note->u.note.pitch) {
+ if (current_glyph->u.notes.liquescentia &
+ (L_AUCTUS_ASCENDENS | L_AUCTUS_DESCENDENS)) {
+ return FinalConnectedAuctus;
+ } else {
+ return FinalConnectedVirga;
+ }
+ } else {
+ return FinalConnectedPunctum;
+ }
+}
+
+static __inline const char *first_note_case(
+ const gregorio_note *const current_note,
+ const gregorio_glyph *const current_glyph)
+{
+ bool ambitus_one = true;
+ if (current_glyph->u.notes.liquescentia >= L_INITIO_DEBILIS) {
+ return InitioDebilis;
+ } else {
+ if (current_note->next && current_note->next->u.note.pitch -
+ current_note->u.note.pitch != 1 &&
+ current_note->u.note.pitch - current_note->next->u.note.pitch
+ != 1) {
+ ambitus_one = false;
+ }
+ switch (current_note->u.note.shape) {
+ case S_ORISCUS_ASCENDENS:
+ case S_ORISCUS_DESCENDENS:
+ return ambitus_one ? InitialOriscus : InitialConnectedOriscus;
+
+ case S_QUILISMA:
+ return ambitus_one ? InitialQuilisma : InitialConnectedQuilisma;
+
+ default:
+ if (ambitus_one) {
+ return InitialPunctum;
+ }
+ if (current_note->u.note.pitch > current_note->next->u.note.pitch) {
+ return InitialConnectedVirga;
+ } else {
+ return InitialConnectedPunctum;
+ }
+ }
+ }
+}
+
+static __inline const char *fused_single_note_case(
+ const gregorio_glyph *const glyph, const char *const unfused_case,
+ const char *const fused_case) {
+ int fuse_to_next_glyph = glyph->u.notes.fuse_to_next_glyph;
+ if (fuse_to_next_glyph < -1 || fuse_to_next_glyph > 1) {
+ return fused_case;
+ }
+ return unfused_case;
+}
+
+static __inline gregorio_vposition below_if_auctus(
+ const gregorio_glyph *const glyph)
+{
+ if (glyph->u.notes.liquescentia &
+ (L_AUCTUS_ASCENDENS | L_AUCTUS_DESCENDENS)) {
+ return VPOS_BELOW;
+ }
+ return VPOS_ABOVE;
+}
+
+static __inline gregorio_vposition above_if_h_episema(
+ const gregorio_note *const note)
+{
+ if (note && note->h_episema_above) {
+ return VPOS_ABOVE;
+ }
+ return VPOS_BELOW;
+}
+
+static __inline gregorio_vposition above_if_fused_next_h_episema(
+ const gregorio_glyph *glyph)
+{
+ const gregorio_glyph *next = gregorio_next_non_texverb_glyph(glyph);
+ gregorio_assert(next && next->type == GRE_GLYPH
+ && is_fused(next->u.notes.liquescentia),
+ above_if_fused_next_h_episema,
+ "expected this glyph to be fused to the next one",
+ return VPOS_BELOW);
+ return above_if_h_episema(next->u.notes.first_note);
+}
+
+static __inline gregorio_vposition above_if_either_h_episema(
+ const gregorio_note *const note)
+{
+ if ((note->previous && note->previous->h_episema_above)
+ || (note->next && note->next->h_episema_above)) {
+ return VPOS_ABOVE;
+ }
+ return VPOS_BELOW;
+}
+
+static __inline gregorio_vposition below_if_next_ambitus_allows(
+ const gregorio_note *const note)
+{
+ assert(note->next);
+
+ if (!note->next || note->u.note.pitch - note->next->u.note.pitch > 2) {
+ return VPOS_BELOW;
+ }
+ return VPOS_ABOVE;
+}
+
+static __inline void low_high_set_lower(const gregorio_glyph *const glyph,
+ gregorio_note *const note)
+{
+ if ((glyph->u.notes.liquescentia & L_DEMINUTUS) ||
+ !(glyph->u.notes.liquescentia
+ & (L_AUCTUS_ASCENDENS | L_AUCTUS_DESCENDENS))) {
+ note->is_lower_note = true;
+ }
+}
+
+static __inline void low_high_set_upper(const gregorio_glyph *const glyph,
+ gregorio_note *const note)
+{
+ if ((glyph->u.notes.liquescentia & L_DEMINUTUS) ||
+ !(glyph->u.notes.liquescentia
+ & (L_AUCTUS_ASCENDENS | L_AUCTUS_DESCENDENS))) {
+ note->is_upper_note = true;
+ }
+}
+
+static __inline void high_low_set_upper(const gregorio_glyph *const glyph,
+ gregorio_note *const note)
+{
+ if (glyph->u.notes.liquescentia & L_DEMINUTUS) {
+ note->is_upper_note = true;
+ }
+}
+
+static __inline void high_low_set_lower(const gregorio_glyph *const glyph,
+ gregorio_note *const note)
+{
+ if (glyph->u.notes.liquescentia & L_DEMINUTUS) {
+ note->is_lower_note = true;
+ }
+}
+
+/* a function that finds the good sign (additional line, vepisema or
+ * hepisema) number, according to the gregoriotex convention (described in
+ * gregoriotex.tex)
+ * this function is REALLY a pain in the ass, but it is sadly necessary */
+/*
+ *
+ * For the first note of a porrectus (flexus), this table summarizes the sign
+ * number (amb2 is the ambitus after the diagonal stroke):
+ *
+ * Porrectus | Porrectus | Porrectus | Porrectus
+ * non-auctus | non-auctus | auctus | Flexus
+ * amb2=1 | amb2>1 | |
+ * =================|=================|==============|==============
+ * PorrNonAuctus | PorrNonAuctus | PorrAuctus | PorrAuctus
+ * ~InitialOne | ~InitialWide | ~InitialAny | ~InitialAny
+ *
+ * For the second note of a torculus resupinus (flexus), this table summarizes
+ * the number (amb1 is the ambitus before the diagonal stroke and amb2 is the
+ * ambitus after the diagonal stroke):
+ *
+ * | | Torculus | Torculus | Torculus | Torculus
+ * | | Resupinus | Resupinus | Resupinus | Resupinus
+ * | Initial | non-auctus | non-auctus | auctus | Flexus
+ * amb1 | Note | amb2=1 | amb2>1 | |
+ * ======|============|=============|=============|============|============
+ * 1 | Punctum | TorcRes | TorcRes | TorcRes | TorcRes
+ * | | ~NonAuctus | ~NonAuctus | ~Auctus | ~Auctus
+ * | | ~Second | ~Second | ~Second | ~Second
+ * | | ~OneOne | ~OneWide | ~OneAny | ~OneAny
+ * ------+------------+-------------+-------------+------------+------------
+ * 1 | I. Debilis | TorcRes | TorcRes | TorcRes | TorcRes
+ * | | ~Debilis | ~Debilis | ~Debilis | ~Debilis
+ * | | ~NonAuctus | ~NonAuctus | ~Auctus | ~Auctus
+ * | | ~Second | ~Second | ~Second | ~Second
+ * | | ~AnyOne | ~AnyWide | ~AnyAny | ~AnyAny
+ * ------+------------+-------------+-------------+------------+------------
+ * 1 | Quilisma | TorcRes | TorcRes | TorcRes | TorcRes
+ * | | ~Quilisma | ~Quilisma | ~Quilisma | ~Quilisma
+ * | | ~NonAuctus | ~NonAuctus | ~Auctus | ~Auctus
+ * | | ~Second | ~Second | ~Second | ~Second
+ * | | ~OneOne | ~OneWide | ~OneAny | ~OneAny
+ * ------+------------+-------------+-------------+------------+------------
+ * 1 | Oriscus | TorcRes | TorcRes | TorcRes | TorcRes
+ * | | ~Oriscus | ~Oriscus | ~Oriscus | ~Oriscus
+ * | | ~NonAuctus | ~NonAuctus | ~Auctus | ~Auctus
+ * | | ~Second | ~Second | ~Second | ~Second
+ * | | ~OneOne | ~OneWide | ~OneAny | ~OneAny
+ * ======|============|=============|=============|============|============
+ * >1 | Punctum | TorcRes | TorcRes | TorcRes | TorcRes
+ * | | ~NonAuctus | ~NonAuctus | ~Auctus | ~Auctus
+ * | | ~Second | ~Second | ~Second | ~Second
+ * | | ~WideOne | ~WideWide | ~WideAny | ~WideAny
+ * ------+------------+-------------+-------------+------------+------------
+ * >1 | I. Debilis | TorcRes | TorcRes | TorcRes | TorcRes
+ * | | ~Debilis | ~Debilis | ~Debilis | ~Debilis
+ * | | ~NonAuctus | ~NonAuctus | ~Auctus | ~Auctus
+ * | | ~Second | ~Second | ~Second | ~Second
+ * | | ~AnyOne | ~AnyWide | ~AnyAny | ~AnyAny
+ * ------+------------+-------------+-------------+------------+------------
+ * >1 | Quilisma | TorcRes | TorcRes | TorcRes | TorcRes
+ * | | ~Quilisma | ~Quilisma | ~Quilisma | ~Quilisma
+ * | | ~NonAuctus | ~NonAuctus | ~Auctus | ~Auctus
+ * | | ~Second | ~Second | ~Second | ~Second
+ * | | ~WideOne | ~WideWide | ~WideAny | ~WideAny
+ * ------+------------+-------------+-------------+------------+------------
+ * >1 | Oriscus | TorcRes | TorcRes | TorcRes | TorcRes
+ * | | ~Oriscus | ~Oriscus | ~Oriscus | ~Oriscus
+ * | | ~NonAuctus | ~NonAuctus | ~Auctus | ~Auctus
+ * | | ~Second | ~Second | ~Second | ~Second
+ * | | ~WideOne | ~WideWide | ~WideAny | ~WideAny
+ *
+ */
+static gregorio_vposition advise_positioning(const gregorio_glyph *const glyph,
+ gregorio_note *const note, int i, const gtex_type type)
+{
+ gregorio_vposition h_episema = VPOS_AUTO, v_episema = VPOS_AUTO;
+ bool v_episema_below_is_lower = false, done;
+ const gregorio_glyph *previous;
+
+ /* no need to clear is_lower_note/is_upper_note because we used calloc */
+
+ switch (type) {
+ case T_PES:
+ case T_QUILISMA_PES:
+ /*
+ * in the case of a pes, we put the episema just under the bottom note
+ */
+ if (i == 1) {
+ if (glyph->u.notes.liquescentia & L_INITIO_DEBILIS) {
+ note->gtex_offset_case = InitioDebilis;
+ h_episema = above_if_h_episema(note->next);
+ } else {
+ note->is_lower_note = true;
+ if (note->u.note.shape == S_QUILISMA) {
+ note->gtex_offset_case = FinalQuilisma;
+ } else {
+ note->gtex_offset_case = FinalPunctum;
+ }
+ h_episema = VPOS_BELOW;
+ }
+ v_episema = VPOS_BELOW;
+ } else { /* i=2 */
+ if (!(glyph->u.notes.liquescentia & L_INITIO_DEBILIS)) {
+ note->is_upper_note = true;
+ }
+ if (glyph->u.notes.liquescentia & L_DEMINUTUS) {
+ note->gtex_offset_case = FinalDeminutus;
+ } else {
+ note->gtex_offset_case = FinalUpperPunctum;
+ }
+ h_episema = VPOS_ABOVE;
+ v_episema = below_if_auctus(glyph);
+ }
+ break;
+ case T_PES_QUADRATUM:
+ case T_PES_QUASSUS:
+ case T_QUILISMA_PES_QUADRATUM:
+ if (i == 1) {
+ note->gtex_offset_case = first_note_case(note, glyph);
+ h_episema = above_if_h_episema(note->next);
+ v_episema = VPOS_BELOW;
+ } else { /* i=2 */
+ if (glyph->u.notes.liquescentia & L_DEMINUTUS) {
+ note->gtex_offset_case = InitioDebilis;
+ } else {
+ note->gtex_offset_case = last_note_case(glyph, FinalPunctum,
+ note, true);
+ }
+ h_episema = VPOS_ABOVE;
+ v_episema = VPOS_BELOW;
+ }
+ break;
+ case T_FLEXUS:
+ case T_FLEXUS_ORISCUS:
+ if (i == 1) {
+ high_low_set_upper(glyph, note);
+ if (type == T_FLEXUS_ORISCUS) {
+ note->gtex_offset_case = InitialConnectedOriscus;
+ } else {
+ if (note->next && note->u.note.pitch -
+ note->next->u.note.pitch == 1) {
+ note->gtex_offset_case = InitialVirga;
+ } else {
+ note->gtex_offset_case = InitialConnectedVirga;
+ }
+ }
+ h_episema = VPOS_ABOVE;
+ if (glyph->u.notes.liquescentia & L_DEMINUTUS) {
+ v_episema = below_if_next_ambitus_allows(note);
+ } else {
+ v_episema = VPOS_BELOW;
+ }
+ } else { /* i=2 */
+ high_low_set_lower(glyph, note);
+ note->gtex_offset_case = last_note_case(glyph, FinalPunctum, note,
+ false);
+ if (glyph->u.notes.liquescentia & L_DEMINUTUS) {
+ h_episema = VPOS_BELOW;
+ } else {
+ h_episema = above_if_h_episema(note->previous);
+ }
+ v_episema = VPOS_BELOW;
+ }
+ break;
+ case T_TORCULUS_RESUPINUS_FLEXUS:
+ done = true;
+ switch (i) {
+ case 1:
+ note->gtex_offset_case = first_note_case(note, glyph);
+ h_episema = above_if_h_episema(note->next);
+ v_episema = VPOS_BELOW;
+ break;
+ case HEPISEMA_FIRST_TWO:
+ case 2:
+ if (glyph->u.notes.liquescentia & L_INITIO_DEBILIS) {
+ note->gtex_offset_case = TorcResDebilisAuctusSecondAnyAny;
+ } else {
+ if (note->u.note.pitch - note->previous->u.note.pitch == 1) {
+ switch (note->previous->u.note.shape) {
+ case S_QUILISMA:
+ note->gtex_offset_case =
+ TorcResQuilismaAuctusSecondOneAny;
+ break;
+ case S_ORISCUS_ASCENDENS:
+ case S_ORISCUS_DESCENDENS:
+ note->gtex_offset_case =
+ TorcResOriscusAuctusSecondOneAny;
+ break;
+ default:
+ note->gtex_offset_case = TorcResAuctusSecondOneAny;
+ break;
+ }
+ } else {
+ switch (note->previous->u.note.shape) {
+ case S_QUILISMA:
+ note->gtex_offset_case =
+ TorcResQuilismaAuctusSecondWideAny;
+ break;
+ case S_ORISCUS_ASCENDENS:
+ case S_ORISCUS_DESCENDENS:
+ note->gtex_offset_case =
+ TorcResOriscusAuctusSecondWideAny;
+ break;
+ default:
+ note->gtex_offset_case = TorcResAuctusSecondWideAny;
+ break;
+ }
+ }
+ }
+ h_episema = VPOS_ABOVE;
+ v_episema = VPOS_BELOW;
+ v_episema_below_is_lower = true;
+ break;
+ default:
+ --i;
+ done = false;
+ break;
+ }
+
+ if (done) {
+ break;
+ }
+ /* else fallthrough to the next case! */
+ case T_PORRECTUS_FLEXUS:
+ switch (i) {
+ case HEPISEMA_FIRST_TWO:
+ case 1:
+ note->gtex_offset_case = PorrAuctusInitialAny;
+ h_episema = VPOS_ABOVE;
+ v_episema = VPOS_BELOW;
+ v_episema_below_is_lower = true;
+ break;
+ case 2:
+ if (glyph->u.notes.liquescentia & L_DEMINUTUS) {
+ note->gtex_offset_case = AntepenultBeforeDeminutus;
+ } else {
+ note->gtex_offset_case = AntepenultBeforePunctum;
+ }
+ h_episema = above_if_either_h_episema(note);
+ v_episema = VPOS_BELOW;
+ break;
+ case 3:
+ high_low_set_upper(glyph, note);
+ h_episema = VPOS_ABOVE;
+ if ((glyph->u.notes.liquescentia & L_DEMINUTUS) && note->next) {
+ v_episema = below_if_next_ambitus_allows(note);
+ note->gtex_offset_case = PenultBeforeDeminutus;
+ } else {
+ v_episema = VPOS_BELOW;
+ note->gtex_offset_case = note_before_last_note_case(glyph,
+ note);
+ }
+ break;
+ default:
+ high_low_set_lower(glyph, note);
+ note->gtex_offset_case = last_note_case(glyph, FinalPunctum, note,
+ false);
+ h_episema = above_if_h_episema(note->previous);
+ v_episema = VPOS_BELOW;
+ break;
+ }
+ break;
+ case T_TORCULUS_RESUPINUS:
+ done = true;
+ switch (i) {
+ case 1:
+ note->gtex_offset_case = first_note_case(note, glyph);
+ h_episema = above_if_h_episema(note->next);
+ v_episema = VPOS_BELOW;
+ break;
+ case HEPISEMA_FIRST_TWO:
+ case 2:
+ if (glyph->u.notes.liquescentia &
+ (L_AUCTUS_ASCENDENS | L_AUCTUS_DESCENDENS)) {
+ /* auctus */
+ if (glyph->u.notes.liquescentia & L_INITIO_DEBILIS) {
+ note->gtex_offset_case = TorcResDebilisAuctusSecondAnyAny;
+ } else {
+ if (note->u.note.pitch -
+ note->previous->u.note.pitch == 1) {
+ switch (note->previous->u.note.shape) {
+ case S_QUILISMA:
+ note->gtex_offset_case =
+ TorcResQuilismaAuctusSecondOneAny;
+ break;
+ case S_ORISCUS_ASCENDENS:
+ case S_ORISCUS_DESCENDENS:
+ note->gtex_offset_case =
+ TorcResOriscusAuctusSecondOneAny;
+ break;
+ default:
+ note->gtex_offset_case = TorcResAuctusSecondOneAny;
+ break;
+ }
+ } else {
+ switch (note->previous->u.note.shape) {
+ case S_QUILISMA:
+ note->gtex_offset_case =
+ TorcResQuilismaAuctusSecondWideAny;
+ break;
+ case S_ORISCUS_ASCENDENS:
+ case S_ORISCUS_DESCENDENS:
+ note->gtex_offset_case =
+ TorcResOriscusAuctusSecondWideAny;
+ break;
+ default:
+ note->gtex_offset_case = TorcResAuctusSecondWideAny;
+ break;
+ }
+ }
+ }
+ } else if (note->next->next->u.note.pitch -
+ note->next->u.note.pitch == 1) {
+ /* non-auctus with a second ambitus of 1 */
+ if (glyph->u.notes.liquescentia & L_INITIO_DEBILIS) {
+ note->gtex_offset_case =
+ TorcResDebilisNonAuctusSecondAnyOne;
+ } else {
+ if (note->u.note.pitch -
+ note->previous->u.note.pitch == 1) {
+ switch (note->previous->u.note.shape) {
+ case S_QUILISMA:
+ note->gtex_offset_case =
+ TorcResQuilismaNonAuctusSecondOneOne;
+ break;
+ case S_ORISCUS_ASCENDENS:
+ case S_ORISCUS_DESCENDENS:
+ note->gtex_offset_case =
+ TorcResOriscusNonAuctusSecondOneOne;
+ break;
+ default:
+ note->gtex_offset_case =
+ TorcResNonAuctusSecondOneOne;
+ break;
+ }
+ } else {
+ switch (note->previous->u.note.shape) {
+ case S_QUILISMA:
+ note->gtex_offset_case =
+ TorcResQuilismaNonAuctusSecondWideOne;
+ break;
+ case S_ORISCUS_ASCENDENS:
+ case S_ORISCUS_DESCENDENS:
+ note->gtex_offset_case =
+ TorcResOriscusNonAuctusSecondWideOne;
+ break;
+ default:
+ note->gtex_offset_case =
+ TorcResNonAuctusSecondWideOne;
+ break;
+ }
+ }
+ }
+ } else {
+ /* non-auctus with a second ambitus of at least 2 */
+ if (glyph->u.notes.liquescentia & L_INITIO_DEBILIS) {
+ note->gtex_offset_case =
+ TorcResDebilisNonAuctusSecondAnyWide;
+ } else {
+ if (note->u.note.pitch -
+ note->previous->u.note.pitch == 1) {
+ switch (note->previous->u.note.shape) {
+ case S_QUILISMA:
+ note->gtex_offset_case =
+ TorcResQuilismaNonAuctusSecondOneWide;
+ break;
+ case S_ORISCUS_ASCENDENS:
+ case S_ORISCUS_DESCENDENS:
+ note->gtex_offset_case =
+ TorcResOriscusNonAuctusSecondOneWide;
+ break;
+ default:
+ note->gtex_offset_case =
+ TorcResNonAuctusSecondOneWide;
+ break;
+ }
+ } else {
+ switch (note->previous->u.note.shape) {
+ case S_QUILISMA:
+ note->gtex_offset_case =
+ TorcResQuilismaNonAuctusSecondWideWide;
+ break;
+ case S_ORISCUS_ASCENDENS:
+ case S_ORISCUS_DESCENDENS:
+ note->gtex_offset_case =
+ TorcResOriscusNonAuctusSecondWideWide;
+ break;
+ default:
+ note->gtex_offset_case =
+ TorcResNonAuctusSecondWideWide;
+ break;
+ }
+ }
+ }
+ }
+ h_episema = VPOS_ABOVE;
+ v_episema = VPOS_BELOW;
+ v_episema_below_is_lower = true;
+ break;
+ default:
+ --i;
+ done = false;
+ break;
+ }
+
+ if (done) {
+ break;
+ }
+ /* else fallthrough to the next case! */
+ case T_PORRECTUS: /* or porrectus-like flexus */
+ switch (i) {
+ case HEPISEMA_FIRST_TWO:
+ case 1:
+ if (glyph->u.notes.fuse_to_next_glyph ||
+ (glyph->u.notes.liquescentia
+ & (L_AUCTUS_ASCENDENS | L_AUCTUS_DESCENDENS))) {
+ /* auctus */
+ note->gtex_offset_case = PorrAuctusInitialAny;
+ } else if (note->next->next->u.note.pitch
+ - note->next->u.note.pitch == 1) {
+ /* non-auctus with a second ambitus of 1 */
+ note->gtex_offset_case = PorrNonAuctusInitialOne;
+ } else {
+ /* non-auctus with a second ambitus of at least 2 */
+ note->gtex_offset_case = PorrNonAuctusInitialWide;
+ }
+ h_episema = VPOS_ABOVE;
+ v_episema = VPOS_BELOW;
+ v_episema_below_is_lower = true;
+ break;
+ case 2:
+ low_high_set_lower(glyph, note);
+ if (glyph->u.notes.liquescentia & L_DEMINUTUS) {
+ note->gtex_offset_case = PenultBeforeDeminutus;
+ } else if (glyph->u.notes.liquescentia
+ & (L_AUCTUS_ASCENDENS | L_AUCTUS_DESCENDENS)) {
+ note->gtex_offset_case = PenultBeforePunctumWide;
+ } else {
+ note->gtex_offset_case = FinalPunctum;
+ }
+ h_episema = above_if_h_episema(note->previous);
+ if (h_episema == VPOS_BELOW && !note->next) {
+ /* handle porrectus-like flexus */
+ h_episema = above_if_fused_next_h_episema(glyph);
+ }
+ v_episema = VPOS_BELOW;
+ break;
+ default: /* case 3 */
+ low_high_set_upper(glyph, note);
+ note->gtex_offset_case = last_note_case(glyph, FinalUpperPunctum,
+ note, true);
+ h_episema = VPOS_ABOVE;
+ v_episema = below_if_auctus(glyph);
+ break;
+ }
+ break;
+ case T_SCANDICUS:
+ switch (i) {
+ case 1:
+ note->gtex_offset_case = first_note_case(note, glyph);
+ h_episema = VPOS_BELOW;
+ v_episema = VPOS_BELOW;
+ break;
+ case 2:
+ low_high_set_lower(glyph, note);
+ if (note->u.note.pitch - note->previous->u.note.pitch == 1) {
+ note->gtex_offset_case = FinalPunctum;
+ } else {
+ note->gtex_offset_case = FinalConnectedPunctum;
+ }
+ h_episema = VPOS_BELOW;
+ v_episema = VPOS_BELOW;
+ break;
+ default:
+ low_high_set_upper(glyph, note);
+ note->gtex_offset_case = last_note_case(glyph, FinalPunctum, note,
+ true);
+ h_episema = VPOS_ABOVE;
+ v_episema = VPOS_BELOW;
+ break;
+ }
+ break;
+ case T_SALICUS_FLEXUS:
+ done = true;
+ v_episema = VPOS_BELOW;
+ switch (i) {
+ case 3:
+ note->gtex_offset_case = note_before_last_note_case(glyph, note);
+ h_episema = VPOS_ABOVE;
+ break;
+ case 4:
+ note->gtex_offset_case = last_note_case(glyph, FinalPunctum, note,
+ false);
+ h_episema = VPOS_BELOW;
+ break;
+ default:
+ done = false;
+ break;
+ }
+ if (done) {
+ break;
+ }
+ /* else fallthrough to the next case! */
+ case T_SALICUS:
+ v_episema = VPOS_BELOW;
+ switch (i) {
+ case 1:
+ note->gtex_offset_case = InitialConnectedPunctum;
+ h_episema = above_if_h_episema(note->next);
+ break;
+ case 2:
+ if (note->next->u.note.pitch - note->u.note.pitch == 1) {
+ note->gtex_offset_case = SalicusOriscusOne;
+ } else {
+ note->gtex_offset_case = SalicusOriscusWide;
+ }
+ h_episema = VPOS_ABOVE;
+ break;
+ default:
+ note->gtex_offset_case = last_note_case(glyph, FinalPunctum, note,
+ false);
+ if (glyph->u.notes.liquescentia & L_DEMINUTUS) {
+ v_episema = VPOS_ABOVE;
+ }
+ h_episema = VPOS_ABOVE;
+ break;
+ }
+ break;
+ case T_TORCULUS_LIQUESCENS:
+ done = true;
+ switch (i) {
+ case 1:
+ note->gtex_offset_case = first_note_case(note, glyph);
+ h_episema = above_if_h_episema(note->next);
+ v_episema = VPOS_BELOW;
+ break;
+ case 2:
+ note->gtex_offset_case =
+ note_before_last_note_case_ignoring_deminutus(note);
+ h_episema = VPOS_ABOVE;
+ v_episema = VPOS_BELOW;
+ break;
+ default:
+ --i;
+ done = false;
+ break;
+ }
+
+ if (done) {
+ break;
+ }
+ /* else fallthrough to the next case! */
+ case T_ANCUS:
+ switch (i) {
+ case 1:
+ note->gtex_offset_case = first_note_case(note, glyph);
+ h_episema = above_if_h_episema(note->next);
+ v_episema = VPOS_BELOW;
+ break;
+ case 2:
+ note->is_upper_note = true;
+ note->gtex_offset_case = note_before_last_note_case(glyph, note);
+ h_episema = VPOS_ABOVE;
+ v_episema = below_if_next_ambitus_allows(note);
+ break;
+ default:
+ note->is_lower_note = true;
+ note->gtex_offset_case = last_note_case(glyph, FinalPunctum, note,
+ false);
+ h_episema = VPOS_BELOW;
+ v_episema = VPOS_BELOW;
+ break;
+ }
+ break;
+ case T_TORCULUS:
+ switch (i) {
+ case 1:
+ note->gtex_offset_case = first_note_case(note, glyph);
+ h_episema = above_if_h_episema(note->next);
+ v_episema = VPOS_BELOW;
+ break;
+ case 2:
+ high_low_set_upper(glyph, note);
+ note->gtex_offset_case = note_before_last_note_case(glyph, note);
+ h_episema = VPOS_ABOVE;
+ if (glyph->u.notes.liquescentia & L_DEMINUTUS) {
+ v_episema = below_if_next_ambitus_allows(note);
+ } else {
+ v_episema = VPOS_BELOW;
+ }
+ break;
+ default:
+ high_low_set_lower(glyph, note);
+ note->gtex_offset_case = last_note_case(glyph, FinalPunctum, note,
+ false);
+ h_episema = above_if_h_episema(note->previous);
+ v_episema = VPOS_BELOW;
+ break;
+ }
+ break;
+ default: /* case T_ONE_NOTE */
+ previous = gregorio_previous_non_texverb_glyph(glyph);
+ if ((!previous || previous->type != GRE_GLYPH
+ || previous->u.notes.fuse_to_next_glyph < 0)
+ && (glyph->u.notes.fuse_to_next_glyph > 0)) {
+ h_episema = above_if_fused_next_h_episema(glyph);
+ } else {
+ h_episema = VPOS_ABOVE;
+ }
+ v_episema = VPOS_BELOW;
+ switch (note->u.note.shape) {
+ case S_PUNCTUM_INCLINATUM_DEMINUTUS:
+ note->gtex_offset_case = FinalInclinatumDeminutus;
+ break;
+ case S_PUNCTUM_INCLINATUM_AUCTUS:
+ case S_PUNCTUM_INCLINATUM_ASCENDENS:
+ case S_PUNCTUM_INCLINATUM_DESCENDENS:
+ note->gtex_offset_case = FinalInclinatum;
+ break;
+ case S_STROPHA:
+ note->gtex_offset_case = FinalStropha;
+ break;
+ case S_QUILISMA:
+ note->gtex_offset_case = fused_single_note_case(glyph,
+ FinalQuilisma, LeadingQuilisma);
+ break;
+ case S_ORISCUS_ASCENDENS:
+ case S_ORISCUS_DESCENDENS:
+ case S_ORISCUS_DEMINUTUS:
+ note->gtex_offset_case = fused_single_note_case(glyph, FinalOriscus,
+ LeadingOriscus);
+ break;
+ case S_VIRGA:
+ note->gtex_offset_case = InitialVirga;
+ break;
+ case S_VIRGA_REVERSA:
+ if (glyph->u.notes.liquescentia == L_AUCTUS_DESCENDENS) {
+ note->gtex_offset_case = FinalVirgaAuctus;
+ } else {
+ note->gtex_offset_case = InitialVirga;
+ }
+ break;
+ case S_LINEA_PUNCTUM:
+ note->gtex_offset_case = FinalLineaPunctum;
+ break;
+ case S_LINEA:
+ note->gtex_offset_case = FinalPunctum;
+ break;
+ case S_FLAT:
+ note->gtex_offset_case = Flat;
+ break;
+ case S_SHARP:
+ note->gtex_offset_case = Sharp;
+ break;
+ case S_NATURAL:
+ note->gtex_offset_case = Natural;
+ break;
+ default:
+ note->gtex_offset_case = last_note_case(glyph,
+ fused_single_note_case(glyph, FinalPunctum, LeadingPunctum),
+ note, true);
+ break;
+ }
+ break;
+ }
+
+ if (note->signs & _V_EPISEMA) {
+ if (note->v_episema_height) {
+ if (note->v_episema_height > note->u.note.pitch) {
+ /* above is always higher because of GregorioTeX's design */
+ note->v_episema_height += (int)VPOS_ABOVE;
+ }
+ }
+ else {
+ note->v_episema_height = note->u.note.pitch + (int)v_episema;
+ if ((v_episema == VPOS_BELOW && v_episema_below_is_lower)
+ || v_episema == VPOS_ABOVE) {
+ /* above is always higher because of GregorioTeX's design */
+ note->v_episema_height += (int)v_episema;
+ }
+ }
+ }
+
+ return h_episema;
+}
+
+static __inline char compute_h_episema_height(
+ const gregorio_glyph *const glyph, const gregorio_note *const note,
+ const gregorio_vposition vpos)
+{
+ char height = note->u.note.pitch;
+
+ if (note->signs & _V_EPISEMA) {
+ if ((vpos == VPOS_ABOVE && note->v_episema_height >= height)
+ || (vpos == VPOS_BELOW && note->v_episema_height <= height)) {
+ height = note->v_episema_height;
+ }
+ }
+
+ if (vpos == VPOS_ABOVE && note->choral_sign
+ && !choral_sign_here_is_low(glyph, note, NULL)) {
+ ++height;
+ if (!is_on_a_line(note->u.note.pitch)) {
+ ++height;
+ }
+ }
+
+ return height + (int)vpos;
+}
+
+static bool is_bridgeable_space(const gregorio_element *const element)
+{
+ if (element->type == GRE_SPACE) {
+ switch (element->u.misc.unpitched.info.space) {
+ case SP_NEUMATIC_CUT:
+ case SP_LARGER_SPACE:
+ case SP_NEUMATIC_CUT_NB:
+ case SP_LARGER_SPACE_NB:
+ return true;
+ default:
+ /* do nothing */
+ break;
+ }
+ }
+
+ return false;
+}
+
+typedef struct height_computation {
+ const gregorio_vposition vpos;
+ const gregorio_sign_orientation orientation;
+ bool (*const is_applicable)(const gregorio_note *);
+ bool (*const is_shown)(const gregorio_note *);
+ bool (*const is_connected)(const gregorio_note *);
+ grehepisema_size (*const get_size)(const gregorio_note *);
+ bool (*const is_better_height)(signed char, signed char);
+ void (*const position)(gregorio_note *, signed char, bool);
+
+ bool active;
+ char height;
+ bool connected;
+ const gregorio_element *start_element;
+ const gregorio_glyph *start_glyph;
+ gregorio_note *start_note;
+ const gregorio_element *last_connected_element;
+ const gregorio_glyph *last_connected_glyph;
+ gregorio_note *last_connected_note;
+ unsigned short adjustment_index;
+} height_computation;
+
+static bool is_h_episema_above_applicable(const gregorio_note *const note)
+{
+ return note->h_episema_above || !note->is_lower_note;
+}
+
+static bool is_h_episema_below_applicable(const gregorio_note *const note)
+{
+ return note->h_episema_below || !note->is_upper_note;
+}
+
+bool gtex_is_h_episema_above_shown(const gregorio_note *const note)
+{
+ return note->h_episema_above;
+}
+
+bool gtex_is_h_episema_below_shown(const gregorio_note *const note)
+{
+ return note->h_episema_below;
+}
+
+static bool is_h_episema_above_connected(const gregorio_note *const note)
+{
+ return note->h_episema_above_connect;
+}
+
+static bool is_h_episema_below_connected(const gregorio_note *const note)
+{
+ return note->h_episema_below_connect;
+}
+
+static grehepisema_size get_h_episema_above_size(
+ const gregorio_note *const note)
+{
+ return note->h_episema_above_size;
+}
+
+static grehepisema_size get_h_episema_below_size(
+ const gregorio_note *const note)
+{
+ return note->h_episema_below_size;
+}
+
+static bool is_h_episema_above_better_height(const signed char new_height,
+ const signed char old_height)
+{
+ return new_height > old_height;
+}
+
+static bool is_h_episema_below_better_height(const signed char new_height,
+ const signed char old_height)
+{
+ return new_height < old_height;
+}
+
+static void adjust_h_episema_above_if_better(gregorio_note *const note,
+ signed char potential_height)
+{
+ if (is_h_episema_above_better_height(potential_height,
+ note->h_episema_above)) {
+ note->h_episema_above = potential_height;
+ }
+}
+
+static void adjust_h_episema_below_if_better(gregorio_note *const note,
+ signed char potential_height)
+{
+ if (is_h_episema_below_better_height(potential_height,
+ note->h_episema_below)) {
+ note->h_episema_below = potential_height;
+ }
+}
+
+static __inline bool has_high_ledger_line(const signed char height,
+ bool is_sign, const gregorio_score *const score)
+{
+ if (is_sign) {
+ return height > score->high_ledger_line_pitch;
+ }
+ return height >= score->high_ledger_line_pitch;
+}
+
+static __inline bool has_low_ledger_line(const signed char height, bool is_sign)
+{
+ if (is_sign) {
+ return height < LOW_LEDGER_LINE_PITCH;
+ }
+ return height <= LOW_LEDGER_LINE_PITCH;
+}
+
+static __inline void start_h_episema(height_computation *const h,
+ const gregorio_element *const element,
+ const gregorio_glyph *const glyph, gregorio_note *const note)
+{
+ h->start_element = element;
+ h->start_glyph = glyph;
+ h->start_note = note;
+ h->active = true;
+ h->height = compute_h_episema_height(glyph, note, h->vpos);
+}
+
+static __inline void position_h_episema(gregorio_note *const note,
+ const height_computation *const h, const bool connect,
+ const bool high_ledger_line, const bool low_ledger_line)
+{
+ const unsigned short adjustment_index =
+ note->he_adjustment_index[h->orientation];
+
+ h->position(note, h->height, connect);
+ if (adjustment_index) {
+ gregorio_hepisema_adjustment *adj = gregorio_get_hepisema_adjustment(
+ adjustment_index);
+ if (adj->pitch_extremum == NO_PITCH
+ || h->is_better_height(h->height, adj->pitch_extremum)) {
+ adj->pitch_extremum = h->height;
+ }
+ }
+ if (!note->high_ledger_specificity && !note->high_ledger_line) {
+ note->high_ledger_line = high_ledger_line;
+ }
+ if (!note->low_ledger_specificity && !note->low_ledger_line) {
+ note->low_ledger_line = low_ledger_line;
+ }
+}
+
+static __inline void next_has_ledger_line(
+ const height_computation *const h, bool *high_ledger_line,
+ bool *low_ledger_line)
+{
+ const gregorio_element *element = h->last_connected_element;
+ const gregorio_glyph *glyph = h->last_connected_glyph;
+ gregorio_note *note = h->last_connected_note;
+ bool eat_upper_note = note->is_lower_note, keep_going = false;
+
+ if (!*high_ledger_line || !*low_ledger_line) {
+ /* going forwards, we must iterate through any lower notes */
+ do {
+ note = note->next;
+ if (!note) {
+ do {
+ glyph = glyph->next;
+ if (!glyph) {
+ do {
+ element = element->next;
+ if (!element) {
+ return;
+ }
+ } while (element->type != GRE_ELEMENT);
+ glyph = element->u.first_glyph;
+ }
+ } while (glyph->type != GRE_GLYPH);
+ note = glyph->u.notes.first_note;
+ }
+
+ *high_ledger_line = *high_ledger_line || note->high_ledger_line;
+ *low_ledger_line = *low_ledger_line || note->low_ledger_line;
+
+ if (keep_going) {
+ keep_going = false;
+ }
+ if (eat_upper_note && note->is_upper_note) {
+ eat_upper_note = false;
+ keep_going = true;
+ }
+ } while ((!*high_ledger_line || !*low_ledger_line)
+ && (note->is_lower_note || keep_going));
+ }
+}
+
+static __inline void previous_has_ledger_line(
+ const height_computation *const h, bool *high_ledger_line,
+ bool *low_ledger_line)
+{
+ const gregorio_element *element = h->start_element;
+ const gregorio_glyph *glyph = h->start_glyph;
+ gregorio_note *note = h->start_note;
+ bool eat_lower_note = note->is_upper_note, keep_going = false;
+
+ if (!*high_ledger_line || !*low_ledger_line) {
+ /* going backwards, we must iterate through any upper notes */
+ do {
+ note = note->previous;
+ if (!note) {
+ do {
+ glyph = glyph->previous;
+ if (!glyph) {
+ do {
+ element = element->previous;
+ if (!element) {
+ return;
+ }
+ } while (element->type != GRE_ELEMENT);
+ for (glyph = element->u.first_glyph; glyph->next;
+ glyph = glyph->next) {
+ /* just iterate to find the last glyph */
+ }
+ }
+ } while (glyph->type != GRE_GLYPH);
+ note = gregorio_glyph_last_note(glyph);
+ }
+ *high_ledger_line = *high_ledger_line || note->high_ledger_line;
+ *low_ledger_line = *low_ledger_line || note->low_ledger_line;
+
+ if (keep_going) {
+ keep_going = false;
+ }
+ if (eat_lower_note && note->is_lower_note) {
+ eat_lower_note = false;
+ keep_going = true;
+ }
+ } while ((!*high_ledger_line || !*low_ledger_line)
+ && (note->is_upper_note || keep_going));
+ }
+}
+
+static __inline void set_h_episema_height(const height_computation *const h,
+ gregorio_note *const end, const gregorio_score *const score)
+{
+ gregorio_note *last_note = NULL;
+
+ const gregorio_element *element = h->start_element;
+ const gregorio_glyph *glyph = h->start_glyph;
+ gregorio_note *note = h->start_note;
+
+ bool high_ledger_line = has_high_ledger_line(h->height, true, score)
+ || has_high_ledger_line(h->height - h->vpos, false, score);
+ bool low_ledger_line = has_low_ledger_line(h->height, true)
+ || has_low_ledger_line(h->height - h->vpos, false);
+
+ next_has_ledger_line(h, &high_ledger_line, &low_ledger_line);
+ previous_has_ledger_line(h, &high_ledger_line, &low_ledger_line);
+
+ for ( ; element; element = element->next) {
+ if (element->type == GRE_ELEMENT) {
+ for (glyph = glyph? glyph : element->u.first_glyph; glyph;
+ glyph = glyph->next) {
+ if (glyph && glyph->type == GRE_GLYPH) {
+ for (note = note? note : glyph->u.notes.first_note; note;
+ note = note->next) {
+ if (end && note == end) {
+ if (last_note) {
+ position_h_episema(last_note, h, false,
+ high_ledger_line, low_ledger_line);
+ }
+ return;
+ }
+ if (h->is_applicable(note)) {
+ position_h_episema(note, h, true, high_ledger_line,
+ low_ledger_line);
+ last_note = note;
+ }
+ }
+ }
+ }
+ } else if (!is_bridgeable_space(element)) {
+ break;
+ }
+ }
+
+ if (last_note) {
+ position_h_episema(last_note, h, false, high_ledger_line,
+ low_ledger_line);
+ }
+}
+
+static __inline bool is_connected_left(const grehepisema_size size) {
+ return size == H_NORMAL || size == H_SMALL_LEFT;
+}
+
+static __inline bool is_connected_right(const grehepisema_size size) {
+ return size == H_NORMAL || size == H_SMALL_RIGHT;
+}
+
+static __inline bool is_connectable_interglyph_ambitus(const signed char first,
+ const signed char second)
+{
+ return abs(first - second) < 3;
+}
+
+static __inline bool is_connectable_interglyph_notes(
+ const gregorio_note *const first, const gregorio_note *const second)
+{
+ return first && second && is_connectable_interglyph_ambitus(
+ first->u.note.pitch, second->u.note.pitch);
+}
+
+static __inline bool has_space_to_left(const gregorio_note *const note) {
+ switch (note->u.note.shape) {
+ case S_PUNCTUM_INCLINATUM_ASCENDENS:
+ case S_PUNCTUM_INCLINATUM_DESCENDENS:
+ case S_PUNCTUM_INCLINATUM_DEMINUTUS:
+ case S_PUNCTUM_INCLINATUM_AUCTUS:
+ return !is_connectable_interglyph_notes(note->previous, note);
+
+ default:
+ return !note->previous;
+ }
+}
+
+static __inline void end_h_episema(height_computation *const h,
+ gregorio_note *const end, const gregorio_score *const score)
+{
+ signed char proposed_height;
+
+ if (h->active) {
+ /* don't let the episema clash with the note before or after */
+ if (is_connected_left(h->get_size(h->start_note))) {
+ if (h->start_note->previous) {
+ if (!has_space_to_left(h->start_note)
+ && h->start_note->previous->type == GRE_NOTE
+ && is_connected_right(h->get_size(
+ h->start_note->previous))) {
+ proposed_height = h->start_note->previous->u.note.pitch + h->vpos;
+ if (h->is_better_height(proposed_height, h->height)) {
+ h->height = proposed_height;
+ }
+ }
+ } else {
+ const gregorio_glyph *previous =
+ gregorio_previous_non_texverb_glyph(h->start_glyph);
+ if (previous && previous->type == GRE_GLYPH
+ && is_fused(h->start_glyph->u.notes.liquescentia)) {
+ gregorio_note *note = gregorio_glyph_last_note(
+ h->start_glyph->previous);
+ if (note->type == GRE_NOTE
+ && is_connected_right(h->get_size(note))) {
+ proposed_height = note->u.note.pitch + h->vpos;
+ if (h->is_better_height(proposed_height, h->height)) {
+ h->height = proposed_height;
+ }
+ }
+ }
+ }
+ }
+ if (end && end->type == GRE_NOTE && is_connected_left(h->get_size(end))
+ && !(end->u.note.liquescentia & L_DEMINUTUS)
+ && h->last_connected_note
+ && is_connected_right(h->get_size(h->last_connected_note))) {
+ if (h->last_connected_note->next) {
+ if (h->last_connected_note->next->type == GRE_NOTE
+ && !has_space_to_left(h->last_connected_note->next)) {
+ gregorio_note *note;
+ /* this loop checks that it's within the same glyph */
+ for (note = end->previous; note; note = note->previous) {
+ if (note == h->start_note) {
+ proposed_height = end->u.note.pitch + h->vpos;
+ if (h->is_better_height(proposed_height,
+ h->height)) {
+ h->height = proposed_height;
+ }
+ break;
+ }
+ }
+ }
+ } else if (is_fused(end->u.note.liquescentia) && !end->previous) {
+ proposed_height = end->u.note.pitch + h->vpos;
+ if (h->is_better_height(proposed_height, h->height)) {
+ h->height = proposed_height;
+ }
+ }
+ }
+
+ set_h_episema_height(h, end, score);
+
+ h->active = false;
+ h->height = 0;
+ h->connected = false;
+ h->start_element = NULL;
+ h->start_glyph = NULL;
+ h->start_note = NULL;
+ h->last_connected_element = NULL;
+ h->last_connected_glyph = NULL;
+ h->last_connected_note = NULL;
+ }
+}
+
+static __inline void compute_h_episema(height_computation *const h,
+ const gregorio_element *const element,
+ const gregorio_glyph *const glyph, gregorio_note *const note,
+ const int i, const gtex_type type, const gregorio_score *const score)
+{
+ grehepisema_size size;
+
+ if (h->is_applicable(note)) {
+ if (h->is_shown(note)) {
+ size = h->get_size(note);
+
+ if (h->active) {
+ const signed char next_height = compute_h_episema_height(
+ glyph, note, h->vpos);
+ if (h->connected && is_connected_left(size)
+ && (i != 1 || is_connectable_interglyph_ambitus(
+ next_height, h->height))) {
+ if (h->is_better_height(next_height, h->height)) {
+ h->height = next_height;
+ }
+ }
+ else {
+ end_h_episema(h, note, score);
+ start_h_episema(h, element, glyph, note);
+ }
+ } else {
+ start_h_episema(h, element, glyph, note);
+ }
+
+ h->connected = h->is_connected(note) && is_connected_right(size);
+ h->last_connected_element = element;
+ h->last_connected_glyph = glyph;
+ h->last_connected_note = note;
+ } else {
+ end_h_episema(h, note, score);
+ }
+ } else if (!h->is_shown(note)) {
+ /* special handling for porrectus shapes because of their shape:
+ * the lower note of the porrectus stroke is normally not applicable,
+ * but we have to end the episema on the upper note if the episema
+ * on the lower note is not shown. */
+ switch(type) {
+ case T_PORRECTUS:
+ case T_PORRECTUS_FLEXUS:
+ if (i == 2) {
+ end_h_episema(h, note, score);
+ }
+ break;
+
+ case T_TORCULUS_RESUPINUS:
+ case T_TORCULUS_RESUPINUS_FLEXUS:
+ if (i == 3) {
+ end_h_episema(h, note, score);
+ }
+ break;
+
+ default:
+ /* do nothing */
+ break;
+ }
+ }
+}
+
+static __inline void compute_note_positioning(height_computation *const above,
+ height_computation *const below, const gregorio_element *const element,
+ const gregorio_glyph *const glyph, gregorio_note *const note,
+ const int i, const gtex_type type, const gregorio_score *const score)
+{
+ gregorio_vposition default_vpos = advise_positioning(glyph, note, i, type);
+
+ if (note->h_episema_above == HEPISEMA_AUTO
+ && note->h_episema_below == HEPISEMA_AUTO) {
+ if (default_vpos == VPOS_BELOW) {
+ note->h_episema_above = HEPISEMA_NONE;
+ }
+ else { /* default_vpos == VPOS_ABOVE */
+ note->h_episema_below = HEPISEMA_NONE;
+ }
+ }
+
+ compute_h_episema(above, element, glyph, note, i, type, score);
+ compute_h_episema(below, element, glyph, note, i, type, score);
+}
+
+static __inline int compute_fused_shift(const gregorio_glyph *glyph)
+{
+ int shift;
+ gregorio_note *first_note, *prev_note;
+ const gregorio_glyph *next, *previous;
+ bool next_is_fused;
+
+ if (!glyph || !is_fused(glyph->u.notes.liquescentia)
+ || glyph->type != GRE_GLYPH
+ || !(first_note = glyph->u.notes.first_note)
+ || first_note->type != GRE_NOTE
+ || !(previous = gregorio_previous_non_texverb_glyph(glyph))
+ || previous->type != GRE_GLYPH
+ || previous->u.notes.liquescentia & TAIL_LIQUESCENTIA_MASK
+ || !previous->u.notes.first_note) {
+ /* base conditions prevent fusion */
+ return 0;
+ }
+
+ next = gregorio_next_non_texverb_glyph(glyph);
+ next_is_fused = next && next->type == GRE_GLYPH
+ && is_fused(next->u.notes.liquescentia);
+
+ switch (glyph->u.notes.glyph_type) {
+ case G_PORRECTUS:
+ case G_PODATUS:
+ if (next_is_fused) {
+ return 0;
+ }
+ /* else it may be fused to the previous note */
+ break;
+
+ case G_PUNCTUM:
+ case G_FLEXA:
+ /* these may be fused to the previous note */
+ break;
+
+ default:
+ /* anything else may not be fused to the previous note */
+ return 0;
+ }
+
+ switch (first_note->u.note.shape) {
+ case S_QUILISMA:
+ case S_QUILISMA_QUADRATUM:
+ case S_FLAT:
+ case S_SHARP:
+ case S_NATURAL:
+ /* if this glyph starts with one of these, it's not fusible */
+ return 0;
+
+ default:
+ /* anything else is potentially fusible */
+ break;
+ }
+
+ switch (previous->u.notes.glyph_type) {
+ case G_PUNCTUM:
+ case G_FLEXA:
+ case G_VIRGA_REVERSA:
+ /* these are potentially fusible to this note */
+ break;
+
+ default:
+ /* everything else is not fusible */
+ return 0;
+ }
+
+ prev_note = gregorio_glyph_last_note(previous);
+
+ gregorio_assert(prev_note->type == GRE_NOTE, compute_fused_shift,
+ "previous note wasn't a note", return 0);
+
+ shift = first_note->u.note.pitch - prev_note->u.note.pitch;
+ gregorio_assert(shift >= -MAX_AMBITUS && shift <= MAX_AMBITUS,
+ compute_fused_shift, "ambitus too large to fuse", return 0);
+
+ if (shift > 0 && previous->u.notes.glyph_type == G_VIRGA_REVERSA) {
+ /* virga reversa cannot fuse upwards */
+ return 0;
+ }
+
+ if (shift < 0) {
+ if (glyph->u.notes.glyph_type == G_PODATUS) {
+ if (glyph->u.notes.liquescentia & L_DEMINUTUS) {
+ /* podatus deminutus may be fused from above */
+ return shift;
+ }
+ switch (first_note->u.note.shape) {
+ case S_ORISCUS_ASCENDENS:
+ case S_ORISCUS_DESCENDENS:
+ case S_ORISCUS_SCAPUS_ASCENDENS:
+ case S_ORISCUS_SCAPUS_DESCENDENS:
+ return shift;
+ default:
+ /* otherwise, podatus may not be fused from above */
+ return 0;
+ }
+ }
+
+ /* the FLEXA check below checks for a porrectus-like flexus, which is
+ * not fusible from above */
+ if ((next_is_fused && glyph->u.notes.glyph_type == G_FLEXA)
+ || glyph->u.notes.glyph_type == G_PORRECTUS
+ || (previous->u.notes.glyph_type == G_PUNCTUM
+ && is_initio_debilis(previous->u.notes.liquescentia))) {
+ /* may not be fused from above */
+ return 0;
+ }
+ }
+
+ return shift;
+}
+
+void gregoriotex_compute_positioning(
+ const gregorio_element *const param_element,
+ const gregorio_score *const score)
+{
+ height_computation above = {
+ /*.vpos =*/ VPOS_ABOVE,
+ /*.orientation =*/ SO_OVER,
+ /*.is_applicable =*/ &is_h_episema_above_applicable,
+ /*.is_shown =*/ &gtex_is_h_episema_above_shown,
+ /*.is_connected =*/ &is_h_episema_above_connected,
+ /*.get_size =*/ &get_h_episema_above_size,
+ /*.is_better_height =*/ &is_h_episema_above_better_height,
+ /*.position =*/ &gregorio_position_h_episema_above,
+
+ /*.active =*/ false,
+ /*.height =*/ 0,
+ /*.connected =*/ false,
+ /*.start_element =*/ NULL,
+ /*.start_glyph =*/ NULL,
+ /*.start_note =*/ NULL,
+ /*.last_connected_element =*/ NULL,
+ /*.last_connected_glyph =*/ NULL,
+ /*.last_connected_note =*/ NULL,
+ /*.adjustment_index =*/ 0,
+ };
+ height_computation below = {
+ /*.vpos =*/ VPOS_BELOW,
+ /*.orientation =*/ SO_UNDER,
+ /*.is_applicable =*/ &is_h_episema_below_applicable,
+ /*.is_shown =*/ &gtex_is_h_episema_below_shown,
+ /*.is_connected =*/ &is_h_episema_below_connected,
+ /*.get_size =*/ &get_h_episema_below_size,
+ /*.is_better_height =*/ &is_h_episema_below_better_height,
+ /*.position =*/ &gregorio_position_h_episema_below,
+
+ /*.active =*/ false,
+ /*.height =*/ 0,
+ /*.connected =*/ false,
+ /*.start_element =*/ NULL,
+ /*.start_glyph =*/ NULL,
+ /*.start_note =*/ NULL,
+ /*.last_connected_element =*/ NULL,
+ /*.last_connected_glyph =*/ NULL,
+ /*.last_connected_note =*/ NULL,
+ /*.adjustment_index =*/ 0,
+ };
+ int i;
+ gtex_alignment ignored;
+ gtex_type type;
+ const gregorio_element *element;
+
+ for (element = param_element; element; element = element->next) {
+ if (element->type == GRE_ELEMENT) {
+ gregorio_glyph *glyph;
+ for (glyph = element->u.first_glyph; glyph;
+ glyph = glyph->next) {
+ if (glyph->type == GRE_GLYPH) {
+ gregorio_note *note;
+ glyph->u.notes.fuse_to_next_glyph = compute_fused_shift(
+ gregorio_next_non_texverb_glyph(glyph));
+ i = 0;
+ gregoriotex_determine_glyph_name(glyph, &ignored, &type);
+ for (note = glyph->u.notes.first_note; note;
+ note = note->next) {
+ if (note->type == GRE_NOTE) {
+ compute_note_positioning(&above, &below, element,
+ glyph, note, ++i, type, score);
+ }
+ }
+ }
+ }
+ } else if (!is_bridgeable_space(element)) {
+ end_h_episema(&above, NULL, score);
+ end_h_episema(&below, NULL, score);
+ }
+ }
+ end_h_episema(&above, NULL, score);
+ end_h_episema(&below, NULL, score);
+}
+
+static __inline void adjust_hepisema(gregorio_note *const note,
+ const gregorio_sign_orientation orientation,
+ bool (*const is_shown)(const gregorio_note *),
+ void (*const adjust_if_better)(gregorio_note *, signed char))
+{
+ const unsigned short adjustment_index =
+ note->he_adjustment_index[orientation];
+
+ if (adjustment_index && is_shown(note)) {
+ gregorio_hepisema_adjustment *adj = gregorio_get_hepisema_adjustment(
+ adjustment_index);
+
+ adjust_if_better(note, adj->pitch_extremum);
+ }
+}
+
+void gregoriotex_compute_cross_syllable_positioning(
+ const gregorio_score *const score)
+{
+ gregorio_syllable *syllable;
+ for (syllable = score->first_syllable; syllable;
+ syllable = syllable->next_syllable) {
+ int voice;
+ for (voice = 0; voice < score->number_of_voices; ++voice) {
+ gregorio_element *element;
+ for (element = syllable->elements[voice]; element;
+ element = element->next) {
+ if (element->type == GRE_ELEMENT) {
+ gregorio_glyph *glyph;
+ for (glyph = element->u.first_glyph; glyph;
+ glyph = glyph->next) {
+ if (glyph->type == GRE_GLYPH) {
+ gregorio_note *note;
+ for (note = glyph->u.notes.first_note; note;
+ note = note->next) {
+ if (note->type == GRE_NOTE) {
+ adjust_hepisema(note, SO_OVER,
+ gtex_is_h_episema_above_shown,
+ adjust_h_episema_above_if_better);
+ adjust_hepisema(note, SO_UNDER,
+ gtex_is_h_episema_below_shown,
+ adjust_h_episema_below_if_better);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/Build/source/texk/gregorio/gregorio-src/src/gregoriotex/gregoriotex-write.c b/Build/source/texk/gregorio/gregorio-src/src/gregoriotex/gregoriotex-write.c
new file mode 100644
index 00000000000..1f9b915a7e4
--- /dev/null
+++ b/Build/source/texk/gregorio/gregorio-src/src/gregoriotex/gregoriotex-write.c
@@ -0,0 +1,4539 @@
+/*
+ * Gregorio is a program that translates gabc files to GregorioTeX
+ * This file contains functions for writing GregorioTeX from Gregorio structures.
+ *
+ * Copyright (C) 2008-2019 The Gregorio Project (see CONTRIBUTORS.md)
+ *
+ * This file is part of Gregorio.
+ *
+ * This program 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.
+ *
+ * This program 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
+ * this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/**
+ * @file
+ * @brief The plugin which writes a GregorioTeX score.
+ */
+
+#include "config.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <time.h>
+#include "bool.h"
+#include "struct.h"
+#include "unicode.h"
+#include "messages.h"
+#include "characters.h"
+#include "plugins.h"
+#include "support.h"
+#include "utf8strings.h"
+
+#include "gregoriotex.h"
+
+#define BUFSIZE 128
+
+/* a structure containing the status */
+typedef struct gregoriotex_status {
+ bool point_and_click;
+
+ /* true if the current_glyph will have an additional line under or not
+ * (useful to determine the length of the bar in case of a flexa starting
+ * at d */
+ bool bottom_line;
+
+ signed char top_height;
+ signed char bottom_height;
+
+ /* indicates if there is a translation on the line */
+ bool translation;
+
+ /* indicates if there is "above lines text" on the line */
+ bool abovelinestext;
+
+ bool suppressed_custos;
+} gregoriotex_status;
+
+#define UNDETERMINED_HEIGHT -127
+
+#define MAX_AMBITUS 5
+static const char *tex_ambitus[] = {
+ "", "One", "Two", "Three", "Four", "Five"
+};
+
+#define SHAPE(NAME) static const char *const SHAPE_##NAME = #NAME
+SHAPE(Ancus);
+SHAPE(AncusLongqueue);
+SHAPE(AscendensOriscus);
+SHAPE(AscendensOriscusLineBL);
+SHAPE(AscendensOriscusLineTL);
+SHAPE(AscendensOriscusScapus);
+SHAPE(AscendensOriscusScapusLongqueue);
+SHAPE(AscendensOriscusScapusOpenqueue);
+SHAPE(AscendensPunctumInclinatum);
+SHAPE(DescendensOriscus);
+SHAPE(DescendensOriscusLineBL);
+SHAPE(DescendensOriscusLineTL);
+SHAPE(DescendensOriscusScapus);
+SHAPE(DescendensOriscusScapusLongqueue);
+SHAPE(DescendensOriscusScapusOpenqueue);
+SHAPE(DescendensPunctumInclinatum);
+SHAPE(Flat);
+SHAPE(Flexus);
+SHAPE(FlexusLongqueue);
+SHAPE(FlexusNobar);
+SHAPE(FlexusOpenqueue);
+SHAPE(FlexusOriscus);
+SHAPE(FlexusOriscusInusitatus);
+SHAPE(FlexusOriscusScapus);
+SHAPE(FlexusOriscusScapusInusitatus);
+SHAPE(FlexusOriscusScapusInusitatusLongqueue);
+SHAPE(FlexusOriscusScapusInusitatusOpenqueue);
+SHAPE(FlexusOriscusScapusLongqueue);
+SHAPE(FlexusOriscusScapusOpenqueue);
+SHAPE(Linea);
+SHAPE(LineaPunctum);
+SHAPE(Natural);
+SHAPE(OriscusDeminutus);
+SHAPE(Pes);
+SHAPE(PesAscendensOriscus);
+SHAPE(PesDescendensOriscus);
+SHAPE(PesQuadratum);
+SHAPE(PesQuadratumLongqueue);
+SHAPE(PesQuadratumOpenqueue);
+SHAPE(PesQuassus);
+SHAPE(PesQuassusInusitatus);
+SHAPE(PesQuassusInusitatusLongqueue);
+SHAPE(PesQuassusInusitatusOpenqueue);
+SHAPE(PesQuassusLongqueue);
+SHAPE(PesQuassusOpenqueue);
+SHAPE(Porrectus);
+SHAPE(PorrectusFlexus);
+SHAPE(PorrectusFlexusLongqueue);
+SHAPE(PorrectusFlexusNobar);
+SHAPE(PorrectusLongqueue);
+SHAPE(PorrectusNobar);
+SHAPE(Punctum);
+SHAPE(PunctumAscendens);
+SHAPE(PunctumDeminutus);
+SHAPE(PunctumDescendens);
+SHAPE(PunctumInclinatumAuctus);
+SHAPE(PunctumInclinatumDeminutus);
+SHAPE(PunctumLineBL);
+SHAPE(PunctumLineTL);
+SHAPE(Quilisma);
+SHAPE(QuilismaPes);
+SHAPE(QuilismaPesQuadratum);
+SHAPE(QuilismaPesQuadratumLongqueue);
+SHAPE(QuilismaPesQuadratumOpenqueue);
+SHAPE(StansPunctumInclinatum);
+SHAPE(Salicus);
+SHAPE(SalicusFlexus);
+SHAPE(SalicusLongqueue);
+SHAPE(Scandicus);
+SHAPE(Sharp);
+SHAPE(Stropha);
+SHAPE(StrophaAucta);
+SHAPE(StrophaAuctaLongtail);
+SHAPE(Torculus);
+SHAPE(TorculusLiquescens);
+SHAPE(TorculusLiquescensQuilisma);
+SHAPE(TorculusQuilisma);
+SHAPE(TorculusResupinus);
+SHAPE(TorculusResupinusQuilisma);
+SHAPE(Virga);
+SHAPE(VirgaLongqueue);
+SHAPE(VirgaOpenqueue);
+SHAPE(VirgaReversa);
+SHAPE(VirgaReversaAscendens);
+SHAPE(VirgaReversaDescendens);
+SHAPE(VirgaReversaLongqueue);
+SHAPE(VirgaReversaLongqueueAscendens);
+SHAPE(VirgaReversaLongqueueDescendens);
+SHAPE(VirgaReversaOpenqueue);
+SHAPE(VirgaReversaOpenqueueAscendens);
+SHAPE(VirgaReversaOpenqueueDescendens);
+
+#define LIQ(NAME) static const char *const LIQ_##NAME = #NAME
+LIQ(Deminutus);
+LIQ(Ascendens);
+LIQ(Descendens);
+LIQ(InitioDebilis);
+LIQ(InitioDebilisDeminutus);
+LIQ(InitioDebilisAscendens);
+LIQ(InitioDebilisDescendens);
+LIQ(Nothing);
+
+#define FUSE(NAME) static const char *const FUSE_##NAME = #NAME
+FUSE(Lower);
+FUSE(LowerOblatus);
+FUSE(Upper);
+FUSE(UpperOblatus);
+FUSE(Down);
+FUSE(Up);
+
+/* the value indicating to GregorioTeX that there is no flat */
+#define NO_KEY_FLAT LOWEST_PITCH
+
+static __inline signed char pitch_value(const signed char height) {
+ /* right now height == pitch, but this function allows us to change
+ * the offset easily */
+ return height;
+}
+
+static __inline int bool_to_int(bool value) {
+ return value? 1 : 0;
+}
+
+typedef enum queuetype {
+ Q_ON_SPACE_BELOW_BOTTOM_LINE,
+ Q_ON_SPACE_ABOVE_BOTTOM_LINE,
+ Q_ON_BOTTOM_LINE,
+ Q_ON_LINE_ABOVE_BOTTOM_LINE
+} queuetype;
+
+static queuetype adjusted_queuetype_of(const gregorio_note *const note,
+ const gregorio_note *const queue_note, const signed char adjustment)
+{
+ switch (note->u.note.pitch + adjustment - LOWEST_PITCH) {
+ case 0:
+ return Q_ON_SPACE_BELOW_BOTTOM_LINE;
+ case 1:
+ return Q_ON_BOTTOM_LINE;
+ case 2:
+ return queue_note->low_ledger_line?
+ Q_ON_SPACE_ABOVE_BOTTOM_LINE : Q_ON_SPACE_BELOW_BOTTOM_LINE;
+ case 3:
+ return queue_note->low_ledger_line?
+ Q_ON_LINE_ABOVE_BOTTOM_LINE : Q_ON_BOTTOM_LINE;
+ case 5:
+ case 7:
+ case 9:
+ case 11:
+ case 13:
+ return Q_ON_LINE_ABOVE_BOTTOM_LINE;
+ default:
+ return Q_ON_SPACE_ABOVE_BOTTOM_LINE;
+ }
+}
+
+static queuetype queuetype_of(const gregorio_note *const note) {
+ return adjusted_queuetype_of(note, note, 0);
+}
+
+static grestyle_style gregoriotex_ignore_style = ST_NO_STYLE;
+static grestyle_style gregoriotex_next_ignore_style = ST_NO_STYLE;
+
+/*
+ * The different liquescentiae are:
+ * 'Nothing'
+ * 'InitioDebilis'
+ * 'Deminutus
+ * 'Ascendens'
+ * 'Descendens'
+ * 'InitioDebilisDeminutus'
+ * 'InitioDebilisAscendens'
+ * 'InitioDebilisDescendens'
+ *
+ * They also are and must be the same as in squarize.py.
+ */
+
+static const char *gregoriotex_determine_liquescentia(
+ gtex_glyph_liquescentia type, const gregorio_glyph *const glyph)
+{
+ gregorio_liquescentia liquescentia = glyph->u.notes.liquescentia;
+
+ switch (type) {
+ case LG_ALL:
+ break;
+ case LG_NO_INITIO:
+ liquescentia &= ~L_INITIO_DEBILIS;
+ break;
+ case LG_ONLY_DEMINUTUS:
+ liquescentia &= L_INITIO_DEBILIS | L_DEMINUTUS;
+ break;
+ case LG_FUSIBLE_INITIO:
+ if (glyph->u.notes.fuse_to_next_glyph) {
+ liquescentia &= L_INITIO_DEBILIS;
+ break;
+ }
+ /* else fall through to next case */
+ case LG_NONE:
+ liquescentia = L_NO_LIQUESCENTIA;
+ break;
+ }
+
+ /* now we convert liquescentia into the good GregorioTeX liquescentia
+ * numbers */
+
+ switch (liquescentia & ~L_FUSED) {
+ case L_DEMINUTUS:
+ return LIQ_Deminutus;
+ case L_AUCTUS_ASCENDENS:
+ return LIQ_Ascendens;
+ case L_AUCTUS_DESCENDENS:
+ return LIQ_Descendens;
+ case L_INITIO_DEBILIS:
+ return LIQ_InitioDebilis;
+ case L_DEMINUTUS_INITIO_DEBILIS:
+ return LIQ_InitioDebilisDeminutus;
+ case L_AUCTUS_ASCENDENS_INITIO_DEBILIS:
+ return LIQ_InitioDebilisAscendens;
+ case L_AUCTUS_DESCENDENS_INITIO_DEBILIS:
+ return LIQ_InitioDebilisDescendens;
+ case L_NO_LIQUESCENTIA:
+ /* break out and return "Nothing" */
+ break;
+ }
+
+ return LIQ_Nothing;
+}
+
+static __inline int compute_ambitus(const gregorio_note *const current_note)
+{
+ int first = current_note->u.note.pitch;
+ int second = current_note->next->u.note.pitch;
+ int ambitus;
+ if (first < second) {
+ ambitus = second - first;
+ } else {
+ ambitus = first - second;
+ }
+ gregorio_assert2(ambitus >= 1 && ambitus <= MAX_AMBITUS, compute_ambitus,
+ "unsupported ambitus: %d", ambitus, return 0);
+ return ambitus;
+}
+
+static const char *compute_glyph_name(const gregorio_glyph *const glyph,
+ const char *shape, const gtex_glyph_liquescentia ltype,
+ bool is_single_note)
+{
+ static char buf[BUFSIZE];
+
+ const char *liquescentia = gregoriotex_determine_liquescentia(ltype, glyph);
+ const gregorio_note *current_note;
+ int ambitus1, ambitus2, ambitus3, fuse_ambitus = 0;
+ const char *fuse_head = "", *fuse_tail = "";
+ const gregorio_glyph *previous = gregorio_previous_non_texverb_glyph(glyph);
+ int fuse_to_next_note, fuse_from_previous_note =
+ (previous && previous->type == GRE_GLYPH)
+ ? previous->u.notes.fuse_to_next_glyph : 0;
+
+ /* then we start making our formula */
+ gregorio_assert(glyph, compute_glyph_name, "called with NULL pointer",
+ return "");
+ gregorio_assert(glyph->u.notes.first_note, compute_glyph_name,
+ "called with a glyph that have no note", return "");
+
+ fuse_to_next_note = glyph->u.notes.fuse_to_next_glyph;
+
+ switch (glyph->u.notes.glyph_type) {
+ case G_PODATUS:
+ case G_PUNCTUM:
+ case G_FLEXA:
+ /* directionally head-fusible */
+ if (fuse_from_previous_note < 0) {
+ if (glyph->u.notes.first_note->u.note.shape != S_QUILISMA
+ && glyph->u.notes.first_note->u.note.shape
+ != S_QUILISMA_QUADRATUM) {
+ if (fuse_from_previous_note < -1) {
+ fuse_head = FUSE_Lower;
+ } else if (glyph->u.notes.first_note->u.note.shape
+ == S_ORISCUS_DESCENDENS
+ || glyph->u.notes.first_note->u.note.shape
+ == S_ORISCUS_SCAPUS_DESCENDENS) {
+ /* fuse_from_previous_note will be -1 here */
+ fuse_head = FUSE_LowerOblatus;
+ }
+ }
+ } else if (fuse_from_previous_note > 0) {
+ if (fuse_from_previous_note > 1) {
+ fuse_head = FUSE_Upper;
+ } else if (glyph->u.notes.first_note->u.note.shape
+ == S_ORISCUS_ASCENDENS
+ || glyph->u.notes.first_note->u.note.shape
+ == S_ORISCUS_SCAPUS_ASCENDENS) {
+ /* fuse_from_previous_note will be 1 here */
+ fuse_head = FUSE_UpperOblatus;
+ }
+ }
+ break;
+
+ default:
+ /* not directionally head-fusible */
+ break;
+ }
+
+ switch (glyph->u.notes.glyph_type) {
+ case G_FLEXA:
+ if (fuse_to_next_note <= 0) {
+ /* a flexa is only fusible up */
+ break;
+ }
+ /* else fall through */
+ case G_VIRGA_REVERSA:
+ case G_PUNCTUM:
+ /* tail-fusible */
+ if (fuse_to_next_note < 0) {
+ fuse_tail = FUSE_Down;
+ fuse_ambitus = -fuse_to_next_note;
+ } else if (fuse_to_next_note > 0) {
+ fuse_tail = FUSE_Up;
+ fuse_ambitus = fuse_to_next_note;
+ }
+
+ if (*fuse_tail && liquescentia == LIQ_Nothing) {
+ liquescentia = "";
+ }
+ break;
+
+ default:
+ /* not tail-fusible */
+ break;
+ }
+
+ if (is_fused(glyph->u.notes.liquescentia)) {
+ if (shape == SHAPE_AscendensOriscusScapus
+ || shape == SHAPE_AscendensOriscusScapusLongqueue
+ || shape == SHAPE_AscendensOriscusScapusOpenqueue) {
+ shape = SHAPE_AscendensOriscus;
+ } else if (shape == SHAPE_DescendensOriscusScapus
+ || shape == SHAPE_DescendensOriscusScapusLongqueue
+ || shape == SHAPE_DescendensOriscusScapusOpenqueue) {
+ shape = SHAPE_DescendensOriscus;
+ } else if (shape == SHAPE_FlexusOriscusScapus
+ || shape == SHAPE_FlexusOriscusScapusLongqueue
+ || shape == SHAPE_FlexusOriscusScapusOpenqueue) {
+ shape = SHAPE_FlexusOriscus;
+ } else if (shape == SHAPE_FlexusOriscusScapusInusitatus
+ || shape == SHAPE_FlexusOriscusScapusInusitatusLongqueue
+ || shape == SHAPE_FlexusOriscusScapusInusitatusOpenqueue) {
+ shape = SHAPE_FlexusOriscusInusitatus;
+ }
+ }
+
+ current_note = glyph->u.notes.first_note;
+ if (is_single_note) {
+ if (liquescentia == LIQ_Nothing) {
+ liquescentia = "";
+ }
+ if (!(*fuse_tail) && fuse_head != FUSE_UpperOblatus
+ && fuse_head != FUSE_LowerOblatus) {
+ /* single note fused shapes have weird names */
+ if (fuse_head == FUSE_Upper) {
+ if (shape == SHAPE_Punctum) {
+ shape = SHAPE_PunctumLineBL;
+ } else if (shape == SHAPE_AscendensOriscus) {
+ shape = SHAPE_AscendensOriscusLineBL;
+ } else if (shape == SHAPE_DescendensOriscus) {
+ shape = SHAPE_DescendensOriscusLineBL;
+ }
+ } else if (fuse_head == FUSE_Lower) {
+ if (shape == SHAPE_Punctum) {
+ shape = SHAPE_PunctumLineTL;
+ } else if (shape == SHAPE_AscendensOriscus) {
+ shape = SHAPE_AscendensOriscusLineTL;
+ } else if (shape == SHAPE_DescendensOriscus) {
+ shape = SHAPE_DescendensOriscusLineTL;
+ }
+ }
+ fuse_head = "";
+ }
+ gregorio_snprintf(buf, BUFSIZE, "%s%s%s%s%s", fuse_head, shape,
+ tex_ambitus[fuse_ambitus], liquescentia, fuse_tail);
+ return buf;
+ }
+ gregorio_assert(current_note->next, compute_glyph_name,
+ "called with a multi-note glyph that has only one note", return "");
+ gregorio_assert((ambitus1 = compute_ambitus(current_note)),
+ compute_glyph_name, "unexpected unison on multi-note glyph",
+ return "");
+ if (is_fused(glyph->u.notes.liquescentia)) {
+ if (shape == SHAPE_Flexus || shape == SHAPE_FlexusLongqueue
+ || shape == SHAPE_FlexusOpenqueue) {
+ if (fuse_to_next_note) {
+ fuse_head = "";
+ }
+ if (*fuse_head) {
+ shape = SHAPE_Flexus;
+ } else {
+ shape = SHAPE_FlexusNobar;
+ }
+ } else if (shape == SHAPE_Porrectus
+ || shape == SHAPE_PorrectusLongqueue) {
+ shape = SHAPE_PorrectusNobar;
+ }
+ } else {
+ if (fuse_to_next_note && (shape == SHAPE_FlexusLongqueue
+ || shape == SHAPE_FlexusOpenqueue)) {
+ /* a porrectus-like flexus has no longqueue variant */
+ shape = SHAPE_Flexus;
+ }
+ }
+ if (shape == SHAPE_SalicusLongqueue && liquescentia != LIQ_Nothing) {
+ /* the salicus queue is at the end of the glyph, and it doesn't exist
+ * for the liquescent forms */
+ shape = SHAPE_Salicus;
+ }
+ current_note = current_note->next;
+ if (!current_note->next) {
+ gregorio_snprintf(buf, BUFSIZE, "%s%s%s%s%s%s", fuse_head, shape,
+ tex_ambitus[ambitus1], tex_ambitus[fuse_ambitus],
+ liquescentia, fuse_tail);
+ return buf;
+ }
+ gregorio_assert((ambitus2 = compute_ambitus(current_note)),
+ compute_glyph_name, "unexpected unison on multi-note glyph",
+ return "");
+ current_note = current_note->next;
+ if (!current_note->next) {
+ gregorio_snprintf(buf, BUFSIZE, "%s%s%s%s%s%s%s", fuse_head, shape,
+ tex_ambitus[ambitus1], tex_ambitus[ambitus2],
+ tex_ambitus[fuse_ambitus], liquescentia, fuse_tail);
+ return buf;
+ }
+ gregorio_assert((ambitus3 = compute_ambitus(current_note)),
+ compute_glyph_name, "unexpected unison on multi-note glyph",
+ return "");
+ gregorio_snprintf(buf, BUFSIZE, "%s%s%s%s%s%s%s%s", fuse_head, shape,
+ tex_ambitus[ambitus1], tex_ambitus[ambitus2], tex_ambitus[ambitus3],
+ tex_ambitus[fuse_ambitus], liquescentia, fuse_tail);
+ return buf;
+}
+
+static const char *fusible_queued_shape(const gregorio_note *const note,
+ const gregorio_glyph *const glyph,
+ const char *const base_shape, const char *const longqueue_shape,
+ const char *const openqueue_shape)
+{
+ const char *name = NULL;
+ if (glyph->u.notes.fuse_to_next_glyph < 0) {
+ /* queue size depends on the following note if fused down */
+ bool ambitus_one = (glyph->u.notes.fuse_to_next_glyph == -1);
+ switch (adjusted_queuetype_of(note, note,
+ glyph->u.notes.fuse_to_next_glyph)) {
+ case Q_ON_SPACE_BELOW_BOTTOM_LINE:
+ if (ambitus_one) {
+ name = openqueue_shape;
+ break;
+ }
+ /* else fall through */
+ case Q_ON_SPACE_ABOVE_BOTTOM_LINE:
+ /* at ambitus one, long and short are swapped becuase the queue where
+ * the second note is on a space is longer than on a line */
+ name = ambitus_one? longqueue_shape : base_shape;
+ break;
+ case Q_ON_BOTTOM_LINE:
+ if (ambitus_one) {
+ name = openqueue_shape;
+ break;
+ }
+ /* else fall through */
+ case Q_ON_LINE_ABOVE_BOTTOM_LINE:
+ /* at ambitus one, long and short are swapped becuase the queue where
+ * the second note is on a line is shorter than on a space */
+ name = ambitus_one? base_shape : longqueue_shape;
+ break;
+ }
+ } else if (glyph->u.notes.fuse_to_next_glyph) {
+ /* TODO (5.0?) handle queue size on upwards fusion */
+ switch (queuetype_of(note)) {
+ case Q_ON_SPACE_BELOW_BOTTOM_LINE:
+ case Q_ON_SPACE_ABOVE_BOTTOM_LINE:
+ case Q_ON_BOTTOM_LINE:
+ name = base_shape;
+ break;
+ case Q_ON_LINE_ABOVE_BOTTOM_LINE:
+ name = longqueue_shape;
+ break;
+ }
+ } else {
+ switch (queuetype_of(note)) {
+ case Q_ON_SPACE_ABOVE_BOTTOM_LINE:
+ name = base_shape;
+ break;
+ case Q_ON_SPACE_BELOW_BOTTOM_LINE:
+ case Q_ON_BOTTOM_LINE:
+ name = openqueue_shape;
+ break;
+ case Q_ON_LINE_ABOVE_BOTTOM_LINE:
+ name = longqueue_shape;
+ break;
+ }
+ }
+ gregorio_not_null(name, fusible_queued_shape, return base_shape);
+ return compute_glyph_name(glyph, name, LG_NONE, true);
+}
+
+static __inline char *code_point(const char *const shape, const bool is_cavum,
+ char *const buf, const size_t bufsize)
+{
+ if (is_cavum) {
+ gregorio_snprintf(buf, bufsize, "\\GreCavum{%s}", shape);
+ } else {
+ gregorio_snprintf(buf, bufsize, "\\GreCP%s", shape);
+ }
+ return buf;
+}
+
+static const char *determine_note_glyph_name(const gregorio_note *const note,
+ const gregorio_glyph *glyph, gtex_alignment *type)
+{
+ static char buf[128], cpbuf[96];
+ const char *name = "";
+
+ gregorio_assert(note, determine_note_glyph_name, "called with NULL pointer",
+ return "");
+
+ *type = AT_ONE_NOTE;
+ switch (note->u.note.shape) {
+ case S_PUNCTUM_INCLINATUM_ASCENDENS:
+ *type = AT_PUNCTUM_INCLINATUM;
+ return SHAPE_AscendensPunctumInclinatum;
+ case S_PUNCTUM_INCLINATUM_STANS:
+ *type = AT_PUNCTUM_INCLINATUM;
+ return SHAPE_StansPunctumInclinatum;
+ case S_PUNCTUM_INCLINATUM_DESCENDENS:
+ *type = AT_PUNCTUM_INCLINATUM;
+ return SHAPE_DescendensPunctumInclinatum;
+ case S_PUNCTUM_INCLINATUM_DEMINUTUS:
+ return SHAPE_PunctumInclinatumDeminutus;
+ case S_PUNCTUM_INCLINATUM_AUCTUS:
+ return SHAPE_PunctumInclinatumAuctus;
+ case S_PUNCTUM:
+ return compute_glyph_name(glyph, SHAPE_Punctum, LG_FUSIBLE_INITIO, true);
+ case S_PUNCTUM_AUCTUS_ASCENDENS:
+ return SHAPE_PunctumAscendens;
+ case S_PUNCTUM_AUCTUS_DESCENDENS:
+ return SHAPE_PunctumDescendens;
+ case S_PUNCTUM_DEMINUTUS:
+ return SHAPE_PunctumDeminutus;
+ case S_LINEA:
+ return SHAPE_Linea;
+ case S_LINEA_PUNCTUM:
+ return SHAPE_LineaPunctum;
+ case S_VIRGA:
+ switch (queuetype_of(note)) {
+ case Q_ON_SPACE_ABOVE_BOTTOM_LINE:
+ return SHAPE_Virga;
+ case Q_ON_SPACE_BELOW_BOTTOM_LINE:
+ case Q_ON_BOTTOM_LINE:
+ return SHAPE_VirgaOpenqueue;
+ case Q_ON_LINE_ABOVE_BOTTOM_LINE:
+ return SHAPE_VirgaLongqueue;
+ } /* all cases return, so this line is not hit; LCOV_EXCL_LINE */
+ case S_VIRGA_REVERSA:
+ switch (note->u.note.liquescentia) {
+ case L_AUCTUS_ASCENDENS:
+ switch (queuetype_of(note)) {
+ case Q_ON_SPACE_BELOW_BOTTOM_LINE:
+ case Q_ON_SPACE_ABOVE_BOTTOM_LINE:
+ name = SHAPE_VirgaReversaAscendens;
+ break;
+ case Q_ON_BOTTOM_LINE:
+ name = SHAPE_VirgaReversaOpenqueueAscendens;
+ break;
+ case Q_ON_LINE_ABOVE_BOTTOM_LINE:
+ name = SHAPE_VirgaReversaLongqueueAscendens;
+ break;
+ }
+ if (note->u.note.pitch - LOWEST_PITCH == 3) {
+ /* if we're on the 'd' line, the queue could be long or short */
+ gregorio_snprintf(buf, sizeof buf,
+ "VirgaReversaAscendensOnDLine{%s}", code_point(name,
+ glyph->u.notes.is_cavum, cpbuf, sizeof cpbuf));
+ return buf;
+ }
+ return name;
+ case L_AUCTUS_DESCENDENS:
+ switch (queuetype_of(note)) {
+ case Q_ON_SPACE_BELOW_BOTTOM_LINE:
+ case Q_ON_SPACE_ABOVE_BOTTOM_LINE:
+ return SHAPE_VirgaReversaDescendens;
+ case Q_ON_BOTTOM_LINE:
+ return SHAPE_VirgaReversaOpenqueueDescendens;
+ case Q_ON_LINE_ABOVE_BOTTOM_LINE:
+ return SHAPE_VirgaReversaLongqueueDescendens;
+ } /* all cases return, so this line is not hit; LCOV_EXCL_LINE */
+ default:
+ return fusible_queued_shape(note, glyph, SHAPE_VirgaReversa,
+ SHAPE_VirgaReversaLongqueue, SHAPE_VirgaReversaOpenqueue);
+ }
+ case S_ORISCUS_ASCENDENS:
+ *type = AT_ORISCUS;
+ return compute_glyph_name(glyph, SHAPE_AscendensOriscus, LG_NONE, true);
+ case S_ORISCUS_DESCENDENS:
+ *type = AT_ORISCUS;
+ return compute_glyph_name(glyph, SHAPE_DescendensOriscus, LG_NONE, true);
+ case S_ORISCUS_DEMINUTUS:
+ *type = AT_ORISCUS;
+ return SHAPE_OriscusDeminutus;
+ case S_QUILISMA:
+ *type = AT_QUILISMA;
+ return compute_glyph_name(glyph, SHAPE_Quilisma, LG_NONE, true);
+ case S_ORISCUS_SCAPUS_ASCENDENS:
+ return fusible_queued_shape(note, glyph, SHAPE_AscendensOriscusScapus,
+ SHAPE_AscendensOriscusScapusLongqueue,
+ SHAPE_AscendensOriscusScapusOpenqueue);
+ case S_ORISCUS_SCAPUS_DESCENDENS:
+ return fusible_queued_shape(note, glyph, SHAPE_DescendensOriscusScapus,
+ SHAPE_DescendensOriscusScapusLongqueue,
+ SHAPE_DescendensOriscusScapusOpenqueue);
+ case S_STROPHA:
+ *type = AT_STROPHA;
+ if (!(note->u.note.liquescentia &
+ (L_AUCTUS_ASCENDENS | L_AUCTUS_DESCENDENS))) {
+ return SHAPE_Stropha;
+ }
+ /* else fall through to next case */
+ case S_STROPHA_AUCTA:
+ *type = AT_STROPHA;
+ switch (queuetype_of(note)) {
+ case Q_ON_SPACE_BELOW_BOTTOM_LINE:
+ case Q_ON_SPACE_ABOVE_BOTTOM_LINE:
+ return SHAPE_StrophaAucta;
+ case Q_ON_BOTTOM_LINE:
+ case Q_ON_LINE_ABOVE_BOTTOM_LINE:
+ return SHAPE_StrophaAuctaLongtail;
+ } /* all cases return, so this line is not hit; LCOV_EXCL_LINE */
+ case S_FLAT:
+ return SHAPE_Flat;
+ case S_SHARP:
+ return SHAPE_Sharp;
+ case S_NATURAL:
+ return SHAPE_Natural;
+ default:
+ /* not reachable unless there's a programming error */
+ /* LCOV_EXCL_START */
+ gregorio_fail2(determine_note_glyph_name,
+ "called with unknown shape: %s",
+ gregorio_shape_to_string(note->u.note.shape));
+ return "";
+ /* LCOV_EXCL_STOP */
+ }
+}
+
+static __inline gregorio_note *first_note_of(
+ const gregorio_glyph *const glyph) {
+ assert(glyph->type == GRE_GLYPH);
+ assert(glyph->u.notes.first_note);
+ assert(glyph->u.notes.first_note->type == GRE_NOTE);
+ return glyph->u.notes.first_note;
+}
+
+static __inline signed char first_pitch_of(const gregorio_glyph *const glyph) {
+ return first_note_of(glyph)->u.note.pitch;
+}
+
+static __inline gregorio_note *second_note_of(
+ const gregorio_glyph *const glyph) {
+ assert(glyph->type == GRE_GLYPH);
+ assert(glyph->u.notes.first_note);
+ assert(glyph->u.notes.first_note->type == GRE_NOTE);
+ assert(glyph->u.notes.first_note->next);
+ assert(glyph->u.notes.first_note->next->type == GRE_NOTE);
+ return glyph->u.notes.first_note->next;
+}
+
+static __inline signed char second_pitch_of(const gregorio_glyph *const glyph) {
+ return second_note_of(glyph)->u.note.pitch;
+}
+
+static __inline gregorio_note *third_note_of(
+ const gregorio_glyph *const glyph) {
+ assert(glyph->type == GRE_GLYPH);
+ assert(glyph->u.notes.first_note);
+ assert(glyph->u.notes.first_note->type == GRE_NOTE);
+ assert(glyph->u.notes.first_note->next);
+ assert(glyph->u.notes.first_note->next->type == GRE_NOTE);
+ assert(glyph->u.notes.first_note->next->next);
+ assert(glyph->u.notes.first_note->next->next->type == GRE_NOTE);
+ return glyph->u.notes.first_note->next->next;
+}
+
+static __inline signed char third_pitch_of(const gregorio_glyph *const glyph) {
+ return third_note_of(glyph)->u.note.pitch;
+}
+
+static __inline const char *porrectus_shape(const gregorio_glyph *const glyph,
+ const char *base_shape, const char *longqueue_shape) {
+ const gregorio_note *const first_note = first_note_of(glyph);
+ if ((glyph->u.notes.glyph_type == G_PORRECTUS
+ && (glyph->u.notes.liquescentia & L_DEMINUTUS))
+ || first_note->u.note.pitch - second_pitch_of(glyph) == 1) {
+ switch (queuetype_of(first_note)) {
+ case Q_ON_SPACE_BELOW_BOTTOM_LINE:
+ case Q_ON_SPACE_ABOVE_BOTTOM_LINE:
+ case Q_ON_BOTTOM_LINE:
+ return base_shape;
+ case Q_ON_LINE_ABOVE_BOTTOM_LINE:
+ return longqueue_shape;
+ }
+ } /* must be optimized out; LCOV_EXCL_LINE */
+ return base_shape;
+}
+
+static __inline const char *flexus_shape(const gregorio_glyph *const glyph,
+ const signed char ambitus, const char *base_shape,
+ const char *longqueue_shape, const char *openqueue_shape) {
+ const bool ambitus_one = (ambitus == 1);
+ switch (adjusted_queuetype_of(second_note_of(glyph), first_note_of(glyph),
+ 0)) {
+ case Q_ON_SPACE_BELOW_BOTTOM_LINE:
+ if (ambitus_one) {
+ return openqueue_shape;
+ }
+ /* else fall through */
+ case Q_ON_SPACE_ABOVE_BOTTOM_LINE:
+ /* at ambitus one, long and short are swapped becuase the queue where
+ * the second note is on a space is longer than on a line */
+ return ambitus_one? longqueue_shape : base_shape;
+ case Q_ON_BOTTOM_LINE:
+ if (ambitus_one) {
+ return openqueue_shape;
+ }
+ /* else fall through */
+ case Q_ON_LINE_ABOVE_BOTTOM_LINE:
+ /* at ambitus one, long and short are swapped becuase the queue where
+ * the second note is on a line is shorter than on a space */
+ return ambitus_one? base_shape : longqueue_shape;
+ }
+ /* not reachable unless there's a programming error */
+ /* LCOV_EXCL_START */
+ gregorio_fail(flexus_shape, "unexpected queue length");
+ return base_shape;
+ /* LCOV_EXCL_STOP */
+}
+
+static __inline const char *quadratum_shape(const gregorio_glyph *const glyph,
+ const char *base_shape, const char *longqueue_shape,
+ const char *openqueue_shape) {
+ const bool ambitus_one =
+ (second_pitch_of(glyph) - first_pitch_of(glyph) == 1);
+ if (!is_tail_liquescentia(glyph->u.notes.liquescentia)) {
+ switch (adjusted_queuetype_of(first_note_of(glyph),
+ second_note_of(glyph), 0)) {
+ case Q_ON_SPACE_BELOW_BOTTOM_LINE:
+ if (ambitus_one) {
+ return openqueue_shape;
+ }
+ /* else fall through */
+ case Q_ON_SPACE_ABOVE_BOTTOM_LINE:
+ /* at ambitus one, long and short are swapped becuase the queue
+ * where the first note is on a space is longer than on a line */
+ return ambitus_one? longqueue_shape : base_shape;
+ case Q_ON_BOTTOM_LINE:
+ if (ambitus_one) {
+ return openqueue_shape;
+ }
+ /* else fall through */
+ case Q_ON_LINE_ABOVE_BOTTOM_LINE:
+ /* at ambitus one, long and short are swapped becuase the queue
+ * where the first note is on a line is shorter than on a space */
+ return ambitus_one? base_shape : longqueue_shape;
+ }
+ /* not reachable unless there's a programming error */
+ /* LCOV_EXCL_START */
+ gregorio_fail(quadratum_shape, "unexpected queue length");
+ /* fall out of the conditional */
+ }
+ /* LCOV_EXCL_STOP */
+ return base_shape;
+}
+
+/* the function that calculates the number of the glyph. It also
+ * calculates the type, used for determining the position of signs. Type is
+ * very basic, it is only the global dimensions : torculus, one_note, etc. */
+
+const char *gregoriotex_determine_glyph_name(const gregorio_glyph *const glyph,
+ gtex_alignment *const type, gtex_type *const gtype)
+{
+ const char *shape = NULL;
+ gtex_glyph_liquescentia ltype;
+ signed char ambitus;
+ const gregorio_note *second_note;
+ gregorio_assert(glyph, gregoriotex_determine_glyph_name,
+ "called with NULL pointer", return "");
+ gregorio_assert(glyph->u.notes.first_note, gregoriotex_determine_glyph_name,
+ "called with a glyph that has no note", return "");
+ *gtype = T_ONE_NOTE;
+ switch (glyph->u.notes.glyph_type) {
+ case G_PODATUS:
+ switch (glyph->u.notes.first_note->u.note.shape) {
+ case S_QUILISMA:
+ *type = AT_QUILISMA;
+ /* the next if is because we made the choice that AUCTUS shapes
+ * look like pes quadratum. */
+ if (glyph->u.notes.liquescentia
+ & (L_AUCTUS_ASCENDENS | L_AUCTUS_DESCENDENS)) {
+ *gtype = T_QUILISMA_PES_QUADRATUM;
+ shape = SHAPE_QuilismaPesQuadratum;
+ } else {
+ *gtype = T_QUILISMA_PES;
+ shape = SHAPE_QuilismaPes;
+ }
+ ltype = LG_NO_INITIO;
+ break;
+ case S_ORISCUS_ASCENDENS:
+ case S_ORISCUS_SCAPUS_ASCENDENS:
+ *type = AT_ORISCUS;
+ *gtype = T_PES_QUASSUS;
+ shape = quadratum_shape(glyph, SHAPE_PesQuassus,
+ SHAPE_PesQuassusLongqueue, SHAPE_PesQuassusOpenqueue);
+ ltype = LG_NO_INITIO;
+ break;
+ case S_ORISCUS_DESCENDENS:
+ case S_ORISCUS_SCAPUS_DESCENDENS:
+ *type = AT_ORISCUS;
+ *gtype = T_PES_QUASSUS;
+ shape = quadratum_shape(glyph, SHAPE_PesQuassusInusitatus,
+ SHAPE_PesQuassusInusitatusLongqueue,
+ SHAPE_PesQuassusInusitatusOpenqueue);
+ ltype = LG_NO_INITIO;
+ break;
+ default:
+ *type = AT_ONE_NOTE;
+ if (glyph->u.notes.liquescentia
+ & (L_AUCTUS_ASCENDENS | L_AUCTUS_DESCENDENS)) {
+ *gtype = T_PES_QUADRATUM;
+ shape = SHAPE_PesQuadratum;
+ } else {
+ *gtype = T_PES;
+ shape = SHAPE_Pes;
+ }
+ ltype = LG_ALL;
+ break;
+ }
+ break;
+ case G_PES_QUADRATUM:
+ switch (glyph->u.notes.first_note->u.note.shape) {
+ case S_QUILISMA:
+ *type = AT_QUILISMA;
+ *gtype = T_QUILISMA_PES_QUADRATUM;
+ shape = quadratum_shape(glyph, SHAPE_QuilismaPesQuadratum,
+ SHAPE_QuilismaPesQuadratumLongqueue,
+ SHAPE_QuilismaPesQuadratumOpenqueue);
+ ltype = LG_NO_INITIO;
+ break;
+ default:
+ *type = AT_ONE_NOTE;
+ *gtype = T_PES_QUADRATUM;
+ shape = quadratum_shape(glyph, SHAPE_PesQuadratum,
+ SHAPE_PesQuadratumLongqueue, SHAPE_PesQuadratumOpenqueue);
+ ltype = LG_ALL;
+ break;
+ }
+ break;
+ case G_PES_ASCENDENS_ORISCUS:
+ *type = AT_ONE_NOTE;
+ *gtype = T_PES_ORISCUS;
+ shape = SHAPE_PesAscendensOriscus;
+ ltype = LG_ALL;
+ break;
+ case G_PES_DESCENDENS_ORISCUS:
+ *type = AT_ONE_NOTE;
+ *gtype = T_PES_ORISCUS;
+ shape = SHAPE_PesDescendensOriscus;
+ ltype = LG_ALL;
+ break;
+ case G_FLEXA:
+ ambitus = first_pitch_of(glyph) - second_pitch_of(glyph);
+ if (glyph->u.notes.liquescentia & L_DEMINUTUS) {
+ *type = AT_FLEXUS_DEMINUTUS;
+ } else {
+ if (ambitus == 1) {
+ *type = AT_FLEXUS_1;
+ } else {
+ *type = AT_FLEXUS;
+ }
+ }
+ switch (glyph->u.notes.first_note->u.note.shape) {
+ case S_ORISCUS_ASCENDENS:
+ *gtype = T_FLEXUS_ORISCUS;
+ shape = SHAPE_FlexusOriscusInusitatus;
+ ltype = LG_NO_INITIO;
+ break;
+
+ case S_ORISCUS_DESCENDENS:
+ *gtype = T_FLEXUS_ORISCUS;
+ shape = SHAPE_FlexusOriscus;
+ ltype = LG_NO_INITIO;
+ break;
+
+ case S_ORISCUS_SCAPUS_ASCENDENS:
+ *gtype = T_FLEXUS_ORISCUS_SCAPUS;
+ shape = flexus_shape(glyph, ambitus,
+ SHAPE_FlexusOriscusScapusInusitatus,
+ SHAPE_FlexusOriscusScapusInusitatusLongqueue,
+ SHAPE_FlexusOriscusScapusInusitatusOpenqueue);
+ ltype = LG_NO_INITIO;
+ break;
+
+ case S_ORISCUS_SCAPUS_DESCENDENS:
+ *gtype = T_FLEXUS_ORISCUS_SCAPUS;
+ shape = flexus_shape(glyph, ambitus, SHAPE_FlexusOriscusScapus,
+ SHAPE_FlexusOriscusScapusLongqueue,
+ SHAPE_FlexusOriscusScapusOpenqueue);
+ ltype = LG_NO_INITIO;
+ break;
+
+ default:
+ *gtype = glyph->u.notes.fuse_to_next_glyph? T_PORRECTUS : T_FLEXUS;
+ shape = flexus_shape(glyph, ambitus, SHAPE_Flexus,
+ SHAPE_FlexusLongqueue, SHAPE_FlexusOpenqueue);
+ ltype = LG_NO_INITIO;
+ break;
+ }
+ break;
+ case G_TORCULUS:
+ *gtype = T_TORCULUS;
+ if (glyph->u.notes.first_note->u.note.shape == S_QUILISMA) {
+ *type = AT_QUILISMA;
+ shape = SHAPE_TorculusQuilisma;
+ ltype = LG_NO_INITIO;
+ } else {
+ *type = AT_ONE_NOTE;
+ shape = SHAPE_Torculus;
+ ltype = LG_ALL;
+ }
+ break;
+ case G_TORCULUS_LIQUESCENS:
+ *gtype = T_TORCULUS_LIQUESCENS;
+ if (glyph->u.notes.first_note->u.note.shape == S_QUILISMA) {
+ *type = AT_QUILISMA;
+ shape = SHAPE_TorculusLiquescensQuilisma;
+ } else {
+ *type = AT_ONE_NOTE;
+ shape = SHAPE_TorculusLiquescens;
+ }
+ ltype = LG_ONLY_DEMINUTUS;
+ break;
+ case G_TORCULUS_RESUPINUS_FLEXUS:
+ *type = AT_ONE_NOTE;
+ *gtype = T_TORCULUS_RESUPINUS_FLEXUS;
+ break;
+ case G_PORRECTUS:
+ *type = AT_PORRECTUS;
+ *gtype = T_PORRECTUS;
+ shape = porrectus_shape(glyph, SHAPE_Porrectus,
+ SHAPE_PorrectusLongqueue);
+ ltype = LG_NO_INITIO;
+ break;
+ case G_TORCULUS_RESUPINUS:
+ *gtype = T_TORCULUS_RESUPINUS;
+ if (glyph->u.notes.first_note->u.note.shape == S_QUILISMA) {
+ *type = AT_QUILISMA;
+ shape = SHAPE_TorculusResupinusQuilisma;
+ } else {
+ *type = AT_ONE_NOTE;
+ shape = SHAPE_TorculusResupinus;
+ }
+ ltype = LG_ALL;
+ break;
+ case G_PORRECTUS_FLEXUS:
+ *type = AT_PORRECTUS;
+ *gtype = T_PORRECTUS_FLEXUS;
+ shape = porrectus_shape(glyph, SHAPE_PorrectusFlexus,
+ SHAPE_PorrectusFlexusLongqueue);
+ ltype = LG_NO_INITIO;
+ break;
+ case G_PORRECTUS_NO_BAR:
+ *type = AT_PORRECTUS;
+ *gtype = T_TORCULUS_RESUPINUS;
+ shape = SHAPE_PorrectusNobar;
+ ltype = LG_NO_INITIO;
+ break;
+ case G_PORRECTUS_FLEXUS_NO_BAR:
+ *type = AT_PORRECTUS;
+ *gtype = T_TORCULUS_RESUPINUS_FLEXUS;
+ shape = SHAPE_PorrectusFlexusNobar;
+ ltype = LG_NO_INITIO;
+ break;
+ case G_ANCUS:
+ *type = AT_ONE_NOTE;
+ gregorio_assert(glyph->u.notes.liquescentia & L_DEMINUTUS,
+ gregoriotex_determine_glyph_name,
+ "encountered non-deminutus ancus", break);
+ second_note = second_note_of(glyph);
+ if (first_pitch_of(glyph) - second_note->u.note.pitch == 1) {
+ *type = AT_FLEXUS_1;
+ } else {
+ *type = AT_FLEXUS;
+ }
+ *gtype = T_ANCUS;
+ ambitus = first_pitch_of(glyph) - second_pitch_of(glyph);
+ shape = flexus_shape(glyph, ambitus, SHAPE_Ancus,
+ SHAPE_AncusLongqueue, SHAPE_Ancus);
+ ltype = LG_ONLY_DEMINUTUS;
+ break;
+ case G_SCANDICUS:
+ *type = AT_ONE_NOTE;
+ *gtype = T_SCANDICUS;
+ shape = SHAPE_Scandicus;
+ ltype = LG_NO_INITIO;
+ break;
+ case G_SALICUS:
+ *type = AT_ONE_NOTE;
+ *gtype = T_SALICUS;
+ ambitus = third_pitch_of(glyph) - second_pitch_of(glyph);
+ shape = flexus_shape(glyph, ambitus, SHAPE_Salicus,
+ SHAPE_SalicusLongqueue, SHAPE_Salicus);
+ ltype = LG_NO_INITIO;
+ break;
+ case G_SALICUS_FLEXUS:
+ *type = AT_ONE_NOTE;
+ *gtype = T_SALICUS_FLEXUS;
+ shape = SHAPE_SalicusFlexus;
+ ltype = LG_NO_INITIO;
+ break;
+ case G_PUNCTUM_INCLINATUM:
+ case G_PUNCTA_INCLINATA:
+ case G_2_PUNCTA_INCLINATA_DESCENDENS:
+ case G_3_PUNCTA_INCLINATA_DESCENDENS:
+ case G_4_PUNCTA_INCLINATA_DESCENDENS:
+ case G_5_PUNCTA_INCLINATA_DESCENDENS:
+ case G_2_PUNCTA_INCLINATA_ASCENDENS:
+ case G_3_PUNCTA_INCLINATA_ASCENDENS:
+ case G_4_PUNCTA_INCLINATA_ASCENDENS:
+ case G_5_PUNCTA_INCLINATA_ASCENDENS:
+ case G_PUNCTUM:
+ case G_STROPHA:
+ case G_VIRGA:
+ case G_VIRGA_REVERSA:
+ case G_STROPHA_AUCTA:
+ case G_DISTROPHA:
+ case G_DISTROPHA_AUCTA:
+ case G_TRISTROPHA:
+ case G_TRISTROPHA_AUCTA:
+ case G_BIVIRGA:
+ case G_TRIVIRGA:
+ case G_ALTERATION:
+ *type = AT_ONE_NOTE;
+ break;
+ default:
+ /* not reachable unless there's a programming error */
+ /* LCOV_EXCL_START */
+ gregorio_fail2(gregoriotex_determine_glyph_name,
+ "called with unknown glyph: %s",
+ gregorio_glyph_type_to_string(glyph->u.notes.glyph_type));
+ break;
+ /* LCOV_EXCL_STOP */
+ }
+ if (shape) {
+ shape = compute_glyph_name(glyph, shape, ltype, false);
+ }
+ /* we fix *type with initio_debilis */
+ if (*type == AT_ONE_NOTE) {
+ if (is_initio_debilis(glyph->u.notes.liquescentia)) {
+ *type = AT_INITIO_DEBILIS;
+ }
+ }
+
+ return shape;
+}
+
+/**
+ * This now does nothing useful, but we keep it here in case it may
+ * be needed in future.
+ */
+static void gregoriotex_write_voice_info(FILE *f, gregorio_voice_info *voice_info)
+{
+ gregorio_assert(f && voice_info, gregoriotex_write_voice_info,
+ "file or voice_info passed as NULL", return);
+}
+
+/* this function indicates if the syllable is the last of the line or score. */
+static bool is_last_of_line(gregorio_syllable *syllable)
+{
+ gregorio_element *current_element = NULL;
+ if (!(syllable->next_syllable) || ((syllable->next_syllable->elements)[0]
+ && (syllable->next_syllable->elements)[0]->type
+ == GRE_END_OF_LINE)) {
+ /* the next syllable start by an end of line */
+ return true;
+ }
+ if (syllable->elements) {
+ current_element = (syllable->elements)[0];
+ while (current_element) {
+ if (current_element->type == GRE_END_OF_LINE) {
+ /* we return true only if the end of line is the last element */
+ if (!(current_element->next)) {
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ current_element = current_element->next;
+ }
+ }
+ }
+ return false;
+}
+
+/*
+ * ! @brief Prints the beginning of each text style
+ */
+static void gtex_write_begin(FILE *f, grestyle_style style)
+{
+ if (style == gregoriotex_ignore_style) {
+ return;
+ }
+ switch (style) {
+ case ST_ITALIC:
+ fprintf(f, "\\GreItalic{");
+ break;
+ case ST_SMALL_CAPS:
+ fprintf(f, "\\GreSmallCaps{");
+ break;
+ case ST_BOLD:
+ fprintf(f, "\\GreBold{");
+ break;
+ case ST_FORCED_CENTER:
+ case ST_CENTER:
+ fprintf(f, "}{");
+ break;
+ case ST_TT:
+ fprintf(f, "\\GreTypewriter{");
+ break;
+ case ST_UNDERLINED:
+ fprintf(f, "\\GreUnderline{");
+ break;
+ case ST_COLORED:
+ fprintf(f, "\\GreColored{");
+ break;
+ case ST_ELISION:
+ fprintf(f, "\\GreElision{");
+ break;
+ case ST_FIRST_WORD:
+ fprintf(f, "\\GreFirstWord{");
+ break;
+ case ST_FIRST_SYLLABLE:
+ fprintf(f, "\\GreFirstSyllable{");
+ break;
+ case ST_FIRST_SYLLABLE_INITIAL:
+ fprintf(f, "\\GreFirstSyllableInitial{");
+ break;
+ case ST_PROTRUSION_FACTOR:
+ fprintf(f, "\\GreProtrusion{");
+ break;
+ case ST_PROTRUSION:
+ fprintf(f, "{");
+ break;
+ default:
+ break;
+ }
+}
+
+/**
+ * @brief Ends each text style
+ */
+static void gtex_write_end(FILE *f, grestyle_style style)
+{
+ if (style == gregoriotex_ignore_style) {
+ return;
+ }
+ switch (style) {
+ case ST_FORCED_CENTER:
+ case ST_CENTER:
+ case ST_SYLLABLE_INITIAL:
+ fprintf(f, "}{");
+ break;
+ case ST_INITIAL:
+ break;
+ default:
+ fprintf(f, "}");
+ break;
+ }
+}
+
+static __inline void tex_escape_text(FILE *const f, const char *text)
+{
+ /* We escape these characters into \string\ddd (where ddd is the decimal
+ * ASCII value of the character) for most escapes, and into \string\n for
+ * newlines. We do it this way to get the "raw" string values through TeX
+ * and into Lua, where the sequences become \ddd and \n respectively and
+ * are translated into their byte values. Lua can then decide whether the
+ * full strings should be evaluated by TeX as TeX or as strings */
+ for (; *text; ++text) {
+ switch (*text) {
+ case '\\':
+ case '{':
+ case '}':
+ case '~':
+ case '%': /* currently, we'll never get %, but handle it anyway */
+ case '#':
+ case '"':
+ /* these characters have special meaning to TeX */
+ fprintf(f, "\\string\\%03d", *text);
+ break;
+ case '\n':
+ fprintf(f, "\\string\\n");
+ break;
+ case '\r':
+ /* ignore */
+ break;
+ default:
+ /* UTF-8 multibyte sequences will fall into here, which is fine */
+ fputc(*text, f);
+ break;
+ }
+ }
+}
+
+static __inline void tex_escape_wtext(FILE *const f, const grewchar *text)
+{
+ /* We escape these characters into \string\ddd (where ddd is the decimal
+ * ASCII value of the character) for most escapes, and into \string\n for
+ * newlines. We do it this way to get the "raw" string values through TeX
+ * and into Lua, where the sequences become \ddd and \n respectively and
+ * are translated into their byte values. Lua can then decide whether the
+ * full strings should be evaluated by TeX as TeX or as strings */
+ for (; *text; ++text) {
+ switch (*text) {
+ case L'\\':
+ case L'{':
+ case L'}':
+ case L'~':
+ case L'%': /* currently, we'll never get %, but handle it anyway */
+ case L'#':
+ case L'"':
+ /* these characters have special meaning to TeX */
+ fprintf(f, "\\string\\%03d", *text);
+ break;
+ /* There is currently no way to get a carriage return or a newline into
+ * an <sp>, but we'll leave this code here, but #if'd out, in case we
+ * need this function in a different context */
+#if 0
+ case L'\n':
+ fprintf(f, "\\string\\n");
+ break;
+ case L'\r':
+ / * ignore * /
+ break;
+#endif
+ default:
+ gregorio_print_unichar(f, *text);
+ break;
+ }
+ }
+}
+
+/*
+ * ! @brief Writes GregorioTeX special characters. This function takes the
+ * special characters as input (i.e. from gabc representation), and writes them
+ * * in GregorioTeX form.
+ */
+static void gtex_write_special_char(FILE *f, const grewchar *special_char)
+{
+ fprintf(f, "\\GreSpecial{");
+ tex_escape_wtext(f, special_char);
+ fprintf(f, "}");
+}
+
+static void gtex_write_verb(FILE *f, const grewchar *first_char)
+{
+ gregorio_print_unistring(f, first_char);
+}
+
+static void gtex_print_char(FILE *f, const grewchar to_print)
+{
+ switch (to_print) {
+ case L'*':
+ fprintf(f, "\\GreStar{}");
+ break;
+ case L'%':
+ /* there's currently no way to get a % into gabc, so this wont be hit,
+ * but we leave it here for safety and possible future use */
+ /* LCOV_EXCL_START */
+ fprintf(f, "\\%%{}");
+ break;
+ /* LCOV_EXCL_STOP */
+ case L'\\':
+ fprintf(f, "\\textbackslash{}");
+ break;
+ case L'&':
+ fprintf(f, "\\&{}");
+ break;
+ case L'#':
+ fprintf(f, "\\#{}");
+ break;
+ case L'+':
+ fprintf(f, "\\GreDagger{}");
+ break;
+ case L'_':
+ fprintf(f, "\\_{}");
+ break;
+ case L'-':
+ fprintf(f, "\\GreHyph{}");
+ break;
+ default:
+ gregorio_print_unichar(f, to_print);
+ break;
+ }
+ return;
+}
+
+/* a function to map the internal ST_* styles to gregoriotex styles as defined
+ * in gregoriotex-syllables.tex */
+static unsigned char gregoriotex_internal_style_to_gregoriotex(
+ const grestyle_style style)
+{
+ switch (style) {
+ case ST_ITALIC:
+ return 1;
+ case ST_BOLD:
+ return 2;
+ case ST_SMALL_CAPS:
+ return 3;
+ case ST_TT:
+ return 4;
+ case ST_UNDERLINED:
+ return 5;
+ case ST_COLORED:
+ return 6;
+ default:
+ /* not reachable unless there's a programming error */
+ /* LCOV_EXCL_START */
+ gregorio_fail2(gregoriotex_internal_style_to_gregoriotex,
+ "unrecognized style: %s", grestyle_style_to_string(style));
+ return 0;
+ /* LCOV_EXCL_STOP */
+ }
+}
+
+/*
+ * A quite hacky function: when we have only one style (typically italic) and
+ * when this style is on all the parts, then we return this style.
+ *
+ */
+static grestyle_style gregoriotex_fix_style(
+ const gregorio_character *current_char)
+{
+ grestyle_style possible_fixed_style = ST_NO_STYLE;
+ grestyle_style in_fixed_style = ST_NO_STYLE;
+ while (current_char) {
+ if (!in_fixed_style) {
+ /* before the first style */
+ if (current_char->is_character) {
+ /* got some character, so no future style can apply to the
+ * entire syllable */
+ return ST_NO_STYLE;
+ }
+ if (current_char->cos.s.type == ST_T_BEGIN) {
+ switch (current_char->cos.s.style) {
+ case ST_VERBATIM:
+ case ST_SPECIAL_CHAR:
+ /* these are pseudo-characters, and if they appear
+ * before the first style, then the style does not
+ * apply to the entire syllable */
+ return ST_NO_STYLE;
+
+ case ST_ITALIC:
+ case ST_BOLD:
+ case ST_TT:
+ case ST_SMALL_CAPS:
+ case ST_UNDERLINED:
+ case ST_COLORED:
+ if (possible_fixed_style) {
+ if (current_char->cos.s.style != possible_fixed_style) {
+ /* found a differing style */
+ return ST_NO_STYLE;
+ } else {
+ /* same style which may apply to the entire
+ * syllable */
+ in_fixed_style = possible_fixed_style;
+ }
+ } else {
+ /* we found a candidate fixed style */
+ in_fixed_style = possible_fixed_style =
+ current_char->cos.s.style;
+ }
+ break;
+
+ default:
+ /* anything else is "transparent" before the first real
+ * or pseudo-character */
+ break;
+ }
+ }
+ /* else it's an end of a style, which we treat as "transparent"
+ * noise */
+ } else {
+ /* we have a possible style */
+ if (!current_char->is_character) {
+ if (current_char->cos.s.type == ST_T_BEGIN) {
+ switch (current_char->cos.s.style) {
+ case ST_ITALIC:
+ case ST_BOLD:
+ case ST_TT:
+ case ST_SMALL_CAPS:
+ case ST_UNDERLINED:
+ case ST_COLORED:
+ if (current_char->cos.s.style != possible_fixed_style) {
+ /* found a differing style */
+ return ST_NO_STYLE;
+ }
+ /* else it's a (nested) open of the same style, which
+ * doesn't change the style; however, nested styles
+ * (should) have been eliminated by now */
+ /* LCOV_EXCL_START */
+ gregorio_fail(gregoriotex_fix_style, "encountered a "
+ "nested style which should have been removed "
+ "by now");
+ break;
+ /* LCOV_EXCL_STOP */
+
+ default:
+ /* anything else is a pseudo-character or a
+ * "transparent" style, which don't affect the fixed
+ * style */
+ break;
+ }
+ } else if (current_char->cos.s.type == ST_T_END) {
+ if (current_char->cos.s.style == possible_fixed_style) {
+ /* we closed the possible fixed style; we don't return
+ * yet to give it the chance to re-open */
+ in_fixed_style = ST_NO_STYLE;
+ }
+ /* else it's a close of something we don't care about or a
+ * style that wasn't opened, so for the purposes of this
+ * function, these are "transparent" and don't affect the
+ * fixed style */
+ }
+ }
+ /* else it's a character, and that doesn't affect the fixed style */
+ }
+ current_char = current_char->next_character;
+ }
+ return possible_fixed_style;
+}
+
+/*
+ * @brief Writes the translation.
+ *
+ * There is no special handling of translation text; that is, we just print the
+ * entire string of text under the normal text line, without considering any
+ * special centering or linebreaks.
+ */
+static __inline void write_translation(FILE *f,
+ gregorio_character *translation)
+{
+ gregorio_not_null(translation, write_translation, return);
+ gregorio_write_text(WTP_NORMAL, translation, f, &gtex_write_verb,
+ &gtex_print_char, &gtex_write_begin, &gtex_write_end,
+ &gtex_write_special_char);
+}
+
+/* a function to compute the height of the flat of a key
+ * the flat is always on the line of the */
+
+static char clef_flat_height(gregorio_clef clef, signed char line, bool flatted)
+{
+ char offset = 6;
+
+ if (!flatted) {
+ return NO_KEY_FLAT;
+ }
+
+ switch (clef) {
+ case CLEF_C:
+ switch (line) {
+ case 1:
+ offset = 2;
+ break;
+ case 2:
+ offset = 4;
+ break;
+ case 3:
+ offset = 6;
+ break;
+ case 4:
+ offset = 8;
+ break;
+ case 5:
+ offset = 10;
+ break;
+ default:
+ /* not reachable unless there's a programming error */
+ /* LCOV_EXCL_START */
+ gregorio_fail2(clef_flat_height, "unknown line number: %d", line);
+ break;
+ /* LCOV_EXCL_STOP */
+ }
+ break;
+ case CLEF_F:
+ switch (line) {
+ case 1:
+ offset = 6;
+ break;
+ case 2:
+ offset = 8;
+ break;
+ case 3:
+ offset = 3;
+ break;
+ case 4:
+ offset = 5;
+ break;
+ case 5:
+ offset = 7;
+ break;
+ default:
+ /* not reachable unless there's a programming error */
+ /* LCOV_EXCL_START */
+ gregorio_fail2(clef_flat_height, "unknown line number: %d", line);
+ break;
+ /* LCOV_EXCL_STOP */
+ }
+ break;
+ default:
+ /* not reachable unless there's a programming error */
+ /* LCOV_EXCL_START */
+ gregorio_fail2(clef_flat_height, "unknown clef type: %d", clef);
+ break;
+ /* LCOV_EXCL_STOP */
+ }
+
+ return pitch_value(LOWEST_PITCH + offset);
+}
+
+static __inline bool is_manual_custos(const gregorio_element *const element)
+{
+ return element->type == GRE_CUSTOS && element->u.misc.pitched.force_pitch;
+}
+
+OFFSET_CASE(BarStandard);
+OFFSET_CASE(BarVirgula);
+OFFSET_CASE(BarDivisioFinalis);
+
+static void write_bar(FILE *f, const gregorio_score *const score,
+ const gregorio_syllable *const syllable,
+ const gregorio_element *const element,
+ const unsigned char first_of_disc)
+{
+ const gregorio_bar type = element->u.misc.unpitched.info.bar;
+ const gregorio_sign signs = element->u.misc.unpitched.special_sign;
+ const bool is_inside_bar = element->next && !is_manual_custos(element->next)
+ && element->next->type != GRE_END_OF_LINE;
+ const bool has_text = !element->previous && syllable->text;
+ /* the type number of function vepisemaorrare */
+ const char *offset_case = BarStandard;
+ signed char far_pitch_adjustment = 0;
+ /* don't use "In" version of bars in the first argument of a GreDiscretionary */
+ if (is_inside_bar && first_of_disc != 1) {
+ fprintf(f, "\\GreIn");
+ } else {
+ fprintf(f, "\\Gre");
+ }
+ switch (type) {
+ case B_VIRGULA:
+ fprintf(f, "Virgula");
+ offset_case = BarVirgula;
+ break;
+ case B_DIVISIO_MINIMA:
+ fprintf(f, "DivisioMinima");
+ break;
+ case B_DIVISIO_MINOR:
+ fprintf(f, "DivisioMinor");
+ break;
+ case B_DIVISIO_MAIOR:
+ fprintf(f, "DivisioMaior");
+ break;
+ case B_DIVISIO_FINALIS:
+ fprintf(f, "DivisioFinalis");
+ offset_case = BarDivisioFinalis;
+ break;
+ case B_DIVISIO_MINOR_D1:
+ fprintf(f, "Dominica{1}");
+ break;
+ case B_DIVISIO_MINOR_D2:
+ fprintf(f, "Dominica{2}");
+ break;
+ case B_DIVISIO_MINOR_D3:
+ fprintf(f, "Dominica{3}");
+ break;
+ case B_DIVISIO_MINOR_D4:
+ fprintf(f, "Dominica{4}");
+ break;
+ case B_DIVISIO_MINOR_D5:
+ fprintf(f, "Dominica{5}");
+ break;
+ case B_DIVISIO_MINOR_D6:
+ fprintf(f, "Dominica{6}");
+ break;
+ case B_DIVISIO_MINOR_D7:
+ fprintf(f, "Dominica{7}");
+ break;
+ case B_DIVISIO_MINOR_D8:
+ fprintf(f, "Dominica{8}");
+ break;
+ case B_VIRGULA_HIGH:
+ fprintf(f, "VirgulaHigh");
+ offset_case = BarVirgula;
+ break;
+ case B_DIVISIO_MINIMA_HIGH:
+ fprintf(f, "DivisioMinimaHigh");
+ break;
+ case B_DIVISIO_MAIOR_DOTTED:
+ fprintf(f, "DivisioMaiorDotted");
+ break;
+ case B_DIVISIO_MINIMIS:
+ fprintf(f, "DivisioMinimis");
+ break;
+ case B_DIVISIO_MINIMIS_HIGH:
+ fprintf(f, "DivisioMinimisHigh");
+ break;
+ default:
+ /* not reachable unless there's a programming error */
+ /* LCOV_EXCL_START */
+ gregorio_fail2(write_bar, "unknown bar type: %d", type);
+ break;
+ /* LCOV_EXCL_STOP */
+ }
+ fprintf(f, "{%c}", has_text? '1' : '0');
+ switch (signs) {
+ case _V_EPISEMA:
+ fprintf(f, "{\\GreBarVEpisema{\\GreOCase%s}}", offset_case);
+ break;
+ case _BAR_H_EPISEMA:
+ fprintf(f, "{\\GreBarBrace{\\GreOCase%s}}", offset_case);
+ break;
+ case _V_EPISEMA_BAR_H_EPISEMA:
+ fprintf(f, "{\\GreBarBrace{\\GreOCase%s}"
+ "\\GreBarVEpisema{\\GreOCase%s}}",
+ offset_case, offset_case);
+ break;
+ default:
+ fprintf(f, "{}");
+ break;
+ }
+ switch (type) {
+ case B_DIVISIO_MINIMIS_HIGH:
+ ++ far_pitch_adjustment;
+ /* fall through */
+
+ case B_VIRGULA_HIGH:
+ case B_DIVISIO_MINIMA_HIGH:
+ ++ far_pitch_adjustment;
+ /* fall through */
+
+ case B_DIVISIO_MINIMIS:
+ ++ far_pitch_adjustment;
+ /* fall through */
+
+ case B_VIRGULA:
+ case B_DIVISIO_MINIMA:
+ {
+ char is_far = '0';
+ const gregorio_element *e;
+ const gregorio_syllable *s;
+ const gregorio_glyph *g;
+ const gregorio_note *n;
+ signed char pitch;
+
+ /* find the prior element */
+ e = element->previous;
+ if (!e) {
+ for (s = syllable->previous_syllable; s; s = s->previous_syllable) {
+ /* loop to find the previous syllable with elements */
+ if (s->elements && *s->elements) {
+ for (e = *s->elements; e->next; e = e->next) {
+ /* just loop to find the last element */
+ }
+ break;
+ }
+ }
+ }
+ if (e && e->type == GRE_ELEMENT) {
+ g = e->u.first_glyph;
+ if (g) {
+ while (g->next) {
+ /* loop to find the last glyph in the prior element */
+ g = g->next;
+ }
+ }
+ if (g && g->type == GRE_GLYPH && (n = g->u.notes.first_note)) {
+ while (n->next) {
+ /* loop to find the last note */
+ n = n->next;
+ }
+ pitch = n->u.note.pitch;
+ if (g->u.notes.liquescentia & L_DEMINUTUS && n->previous
+ && n->previous->u.note.pitch > pitch) {
+ pitch = n->previous->u.note.pitch;
+ }
+ if (pitch < score->virgula_far_pitch + far_pitch_adjustment) {
+ /* find next element */
+ e = element->next;
+ if (!e) {
+ for (s = syllable->next_syllable; s;
+ s = s->next_syllable) {
+ /* loop to find the next syllable with elements */
+ if (s->elements && *s->elements) {
+ e = *s->elements;
+ break;
+ }
+ }
+ }
+ if (e) {
+ if (e->type == GRE_END_OF_LINE) {
+ is_far = '1';
+ } else if (e->type == GRE_ELEMENT) {
+ g = e->u.first_glyph;
+ if (g && g->type == GRE_GLYPH) {
+ n = g->u.notes.first_note;
+ if (n) {
+ if (g->u.notes.glyph_type == G_PODATUS
+ && n->next) {
+ n = n->next;
+ }
+ if (n->u.note.pitch
+ < score->virgula_far_pitch
+ + far_pitch_adjustment) {
+ is_far = '1';
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ fprintf(f, "{%c}%%\n", is_far);
+ }
+ break;
+
+ default:
+ fprintf(f, "%%\n");
+ break;
+ }
+}
+
+static __inline char *suppose_high_ledger_line(const gregorio_note *const note)
+{
+ return note->high_ledger_line? "\\GreSupposeHighLedgerLine" : "";
+}
+
+static __inline char *suppose_low_ledger_line(const gregorio_note *const note)
+{
+ return note->low_ledger_line? "\\GreSupposeLowLedgerLine" : "";
+}
+
+/*
+ * ! @brief Writes augmentum duplexes (double dots) We suppose we are on the
+ * last note. \n The algorithm is the following: if there is a previous note,
+ * we consider that the two puncta of the augumentum duplex must correspond to
+ * the last note and the previous note. If we are adding to a single note
+ * glyph, which would be weird but sure why not, we just typeset two puncta
+ * spaced of 2.
+ */
+static void write_auctum_duplex(FILE *f,
+ const gregorio_note *const current_note)
+{
+ char pitch = current_note->u.note.pitch;
+ char previous_pitch = 0;
+ /* second_pitch is the second argument of the \augmentumduplex macro,
+ * that's what this function is all about. */
+ char second_pitch = 0;
+ /* this variable will be set to 1 if we are on the note before the last
+ * note of a podatus or a porrectus or a torculus resupinus */
+ unsigned char special_punctum = 0;
+ if (current_note->previous) {
+ if (current_note->previous->u.note.pitch - current_note->u.note.pitch ==
+ -1
+ || current_note->previous->u.note.pitch -
+ current_note->u.note.pitch == 1) {
+ special_punctum = 1;
+ }
+ second_pitch = current_note->previous->u.note.pitch;
+ previous_pitch = second_pitch;
+ }
+
+ if (!previous_pitch || previous_pitch == pitch) {
+ if (is_on_a_line(pitch)) {
+ second_pitch = pitch;
+ special_punctum = 1;
+ } else {
+ second_pitch = pitch + 1;
+ }
+ }
+ /* the first argument should always be the lowest one, that's what we do
+ * here: */
+ if (pitch > second_pitch) {
+ previous_pitch = pitch;
+ pitch = second_pitch;
+ second_pitch = previous_pitch;
+ }
+ /* maybe the third argument should be changed */
+ fprintf(f, "\\GreAugmentumDuplex{%d}{%d}{%d}%%\n", pitch_value(pitch),
+ pitch_value(second_pitch), special_punctum);
+}
+
+/**
+ * @brief Adds a dot.
+ *
+ * Writes \c \\GrePunctumMora in the gtex file, with the appropriate arguments. You might think this function
+ * more straightforward than it actually is...
+ */
+static void write_punctum_mora(FILE *f, const gregorio_glyph *glyph,
+ const gregorio_note *current_note, const int fuse_to_next_note)
+{
+ /* in this if we consider that the puncta are only on the last two notes
+ * (maybe it would be useful to consider it more entirely, but it would be
+ * really weird...) */
+ /* the variable that will be set to true if we have to shift the punctum
+ * inclinatum before the last note */
+ bool shift_before = false;
+ /* this variable will be set to 1 if we are on the note before the last
+ * note of a podatus or a porrectus or a torculus resupinus */
+ unsigned char special_punctum = 0;
+ /* 0 if space is normal, 1 if there should be no space after a punctum */
+ unsigned char no_space = 0;
+ /* the pitch where to set the punctum */
+ char pitch = current_note->u.note.pitch;
+ /* a variable to know if we are on a punctum inclinatum or not */
+ unsigned char punctum_inclinatum = 0;
+ /* a temp variable */
+ const gregorio_note *tmpnote;
+ /* we go into this switch only if it is the note before the last note */
+ if (current_note->next) {
+ switch (glyph->u.notes.glyph_type) {
+ case G_FLEXA:
+ case G_TORCULUS:
+ case G_TORCULUS_RESUPINUS_FLEXUS:
+ case G_PORRECTUS_FLEXUS:
+ if (!(glyph->u.notes.liquescentia & L_DEMINUTUS)) {
+ shift_before = true;
+ }
+ if (is_between_lines(pitch)
+ && pitch - current_note->next->u.note.pitch == 1) {
+ special_punctum = 2;
+ }
+ break;
+ case G_PODATUS:
+ if ((current_note->u.note.shape != S_PUNCTUM
+ && current_note->u.note.shape != S_QUILISMA)
+ || (glyph->u.notes.liquescentia
+ & (L_AUCTUS_DESCENDENS | L_AUCTUS_ASCENDENS))) {
+ shift_before = true;
+ /* fine tuning */
+ if (current_note->next->u.note.pitch -
+ current_note->u.note.pitch == 1) {
+ if (is_on_a_line(current_note->u.note.pitch)) {
+ special_punctum = 1;
+ } else {
+ pitch = current_note->u.note.pitch - 1;
+ }
+ }
+ } else {
+ /* case for f.g */
+ if (current_note->next->u.note.pitch -
+ current_note->u.note.pitch == 1) {
+ special_punctum = 1;
+ }
+ }
+ break;
+ case G_PES_QUADRATUM:
+ shift_before = true;
+ if (current_note->next->u.note.pitch - current_note->u.note.pitch ==
+ 1) {
+ if (is_on_a_line(current_note->u.note.pitch)) {
+ special_punctum = 1;
+ } else {
+ pitch = current_note->u.note.pitch - 1;
+ }
+ }
+ break;
+ case G_PORRECTUS:
+ case G_TORCULUS_RESUPINUS:
+ /* this case is only for the note before the previous note */
+ if ((current_note->next->u.note.pitch -
+ current_note->u.note.pitch == -1
+ || current_note->next->u.note.pitch -
+ current_note->u.note.pitch == 1)
+ && !(current_note->next->next))
+ special_punctum = 1;
+ break;
+ default:
+ break;
+ }
+ }
+ /* we enter here in any case */
+ switch (glyph->u.notes.glyph_type) {
+ case G_PUNCTA_INCLINATA:
+ case G_2_PUNCTA_INCLINATA_DESCENDENS:
+ case G_3_PUNCTA_INCLINATA_DESCENDENS:
+ case G_4_PUNCTA_INCLINATA_DESCENDENS:
+ case G_5_PUNCTA_INCLINATA_DESCENDENS:
+ case G_TORCULUS:
+ if (!current_note->next) {
+ special_punctum = 1;
+ }
+ break;
+ default:
+ break;
+ }
+ switch (current_note->u.note.shape) {
+ case S_PUNCTUM_INCLINATUM_ASCENDENS:
+ case S_PUNCTUM_INCLINATUM_STANS:
+ case S_PUNCTUM_INCLINATUM_DESCENDENS:
+ case S_PUNCTUM_INCLINATUM_DEMINUTUS:
+ punctum_inclinatum = 1;
+ break;
+ default:
+ break;
+ }
+ /* when the punctum mora is on a note on a line, and the prior note is on
+ * the space immediately above, the dot is placed on the space below the
+ * line instead */
+ if (current_note->previous
+ && (current_note->previous->u.note.pitch -
+ current_note->u.note.pitch == 1)
+ && is_on_a_line(current_note->u.note.pitch)
+ && (current_note->previous->signs == _PUNCTUM_MORA
+ || current_note->previous->signs == _V_EPISEMA_PUNCTUM_MORA
+ || current_note->previous->choral_sign)) {
+ special_punctum = 1;
+ }
+
+ if (current_note->mora_vposition) {
+ special_punctum = current_note->mora_vposition == VPOS_BELOW? 1 : 0;
+ }
+
+ if (shift_before) {
+ if (current_note->next->u.note.pitch - current_note->u.note.pitch == -1
+ || current_note->next->u.note.pitch -
+ current_note->u.note.pitch == 1) {
+ fprintf(f, "\\GrePunctumMora{%d}{3}{%d}{%d}%%\n",
+ pitch_value(pitch), special_punctum, punctum_inclinatum);
+ } else {
+ fprintf(f, "\\GrePunctumMora{%d}{2}{%d}{%d}%%\n",
+ pitch_value(pitch), special_punctum, punctum_inclinatum);
+ }
+ return;
+ }
+ /* There are two special cases. The first: if the next glyph is a
+ * ZERO_WIDTH_SPACE, and the current glyph is a PES, and the punctum mora
+ * is on the first note, and the first note of the next glyph is at least
+ * two (or three depending on something) pitches higher than the current
+ * note.
+ * You'll all have understood, this case is quite rare... but when it
+ * appears, we pass 1 as a second argument of \punctummora so that it
+ * removes the space introduced by the punctummora. */
+ if (glyph->u.notes.glyph_type == G_PODATUS && glyph->next
+ && glyph->next->type == GRE_SPACE
+ && glyph->next->u.misc.unpitched.info.space == SP_ZERO_WIDTH
+ && current_note->next && glyph->next->next
+ && glyph->next->next->type == GRE_GLYPH
+ && glyph->next->next->u.notes.first_note
+ && (glyph->next->next->u.notes.first_note->u.note.pitch -
+ current_note->u.note.pitch > 1)) {
+ fprintf(f, "\\GrePunctumMora{%d}{1}{%d}{%d}%%\n", pitch_value(pitch),
+ special_punctum, punctum_inclinatum);
+ return;
+ }
+ /* if there is a punctum or a auctum dumplex on a note after, we put a
+ * zero-width punctum */
+ tmpnote = current_note->next;
+ while (tmpnote) {
+ if (tmpnote->signs == _PUNCTUM_MORA || tmpnote->signs == _AUCTUM_DUPLEX
+ || tmpnote->signs == _V_EPISEMA_PUNCTUM_MORA
+ || tmpnote->signs == _V_EPISEMA_AUCTUM_DUPLEX
+ || tmpnote->choral_sign) {
+ no_space = 1;
+ break;
+ }
+ tmpnote = tmpnote->next;
+ }
+ /* use a special no-space punctum mora for ascending fusion */
+ if (fuse_to_next_note) {
+ no_space = 1;
+ if (fuse_to_next_note > 0) {
+ special_punctum = 1;
+ }
+ }
+
+ /* the normal operation */
+ fprintf(f, "\\GrePunctumMora{%d}{%d}{%d}{%d}%%\n", pitch_value(pitch),
+ no_space, special_punctum, punctum_inclinatum);
+}
+
+static __inline int get_punctum_inclinatum_space_case(
+ const gregorio_note *const note)
+{
+ char temp;
+
+ switch (note->u.note.shape) {
+ case S_PUNCTUM_INCLINATUM_ASCENDENS:
+ if (note->previous) {
+ /* means that it is not the first note of the puncta inclinata
+ * sequence */
+ temp = note->previous->u.note.pitch - note->u.note.pitch;
+ /* negative values = ascending ambitus */
+ /* not sure we ever need to consider a larger ambitus here */
+ switch (temp) {
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ return 25;
+ case -1:
+ default:
+ return 12;
+ case -2:
+ return 14;
+ case -3:
+ case -4:
+ return 15;
+ }
+ }
+ break;
+ case S_PUNCTUM_INCLINATUM_STANS:
+ if (note->previous) {
+ /* means that it is not the first note of the puncta inclinata
+ * sequence */
+ return 26;
+ }
+ break;
+ case S_PUNCTUM_INCLINATUM_DESCENDENS:
+ if (note->previous) {
+ /* means that it is not the first note of the puncta inclinata
+ * sequence */
+ temp = note->previous->u.note.pitch - note->u.note.pitch;
+ /* negative values = ascending ambitus */
+ /* not sure we ever need to consider a larger ambitus here */
+ switch (temp) {
+ case 1:
+ default:
+ return 3;
+ case 2:
+ return 10;
+ case 3:
+ case 4:
+ return 11;
+ case -1:
+ case -2:
+ case -3:
+ case -4:
+ return 24;
+ }
+ }
+ break;
+ case S_PUNCTUM_INCLINATUM_DEMINUTUS:
+ if (note->previous) {
+ /* means that it is not the first note of the puncta inclinata
+ * sequence */
+ temp = note->previous->u.note.pitch - note->u.note.pitch;
+ if (temp < -2) {
+ return 15;
+ } else if (temp > 2) {
+ return 11;
+ } else {
+ if (note->previous
+ && note->previous->u.note.shape ==
+ S_PUNCTUM_INCLINATUM_DEMINUTUS) {
+ if (temp < -1) {
+ /* really if the ambitus = 3rd at this point */
+ return 14;
+ } else if (temp > 1) {
+ /* really if the ambitus = 3rd at this point */
+ return 10;
+ } else {
+ /* temp == 0, so there is no ascending case */
+ return 8;
+ }
+ } else {
+ if (temp < 0) {
+ /* puncta inclinatum followed by puncta inclinatum
+ * debilis */
+ return 13;
+ } else if (temp > 0) {
+ /* puncta inclinatum followed by puncta inclinatum
+ * debilis */
+ return 7;
+ }
+ }
+ }
+ } /* LCOV_EXCL_LINE */
+ break;
+ case S_PUNCTUM_INCLINATUM_AUCTUS:
+ if (note->previous) {
+ /* means that it is not the first note of the puncta inclinata
+ * sequence */
+ temp = note->previous->u.note.pitch - note->u.note.pitch;
+ if (temp < -1 || temp > 1) {
+ /* this is the normal interglyph space, so we'll use it for
+ * either direction */
+ return 1;
+ } else {
+ /* temp == 0, so there is no ascending case */
+ /* we approximate that it is the same space */
+ return 3;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+static __inline int get_punctum_inclinatum_to_nobar_space_case(
+ const gregorio_glyph *const glyph)
+{
+ if (glyph->u.notes.glyph_type <= G_PUNCTA_INCLINATA) {
+ const gregorio_glyph *next = gregorio_next_non_texverb_glyph(glyph);
+ if (next && next->type == GRE_GLYPH
+ && (next->u.notes.glyph_type == G_PUNCTUM
+ || (next->u.notes.glyph_type == G_FLEXA
+ && !next->u.notes.fuse_to_next_glyph))) {
+ int descent;
+ const gregorio_note *note = gregorio_glyph_last_note(glyph);
+ descent = note->u.note.pitch -
+ glyph->next->u.notes.first_note->u.note.pitch;
+ /* a negative descent is an ascent */
+ switch(descent) {
+ case -1:
+ return 19;
+ case 1:
+ return 16;
+ case -2:
+ return 20;
+ case 2:
+ return 17;
+ case -3:
+ case -4:
+ return 21;
+ case 3:
+ case 4:
+ return 18;
+ }
+ }
+ }
+
+ return -1;
+}
+
+static __inline void write_single_hepisema(FILE *const f, int hepisema_case,
+ const gregorio_note *const note, gregorio_sign_orientation orientation,
+ bool connect, char height, const grehepisema_size size, const int i,
+ const gregorio_glyph *const glyph,
+ const int porrectus_long_episema_index,
+ bool (*const is_episema_shown)(const gregorio_note *))
+{
+ if (height) {
+ const gregorio_hepisema_adjustment *adj =
+ gregorio_get_hepisema_adjustment(
+ note->he_adjustment_index[orientation]);
+ char ambitus = 0;
+ char size_arg;
+
+ switch (size) {
+ case H_SMALL_LEFT:
+ size_arg = 'l';
+ connect = false;
+ break;
+ case H_SMALL_CENTRE:
+ size_arg = 'c';
+ connect = false;
+ break;
+ case H_SMALL_RIGHT:
+ size_arg = 'r';
+ break;
+ default:
+ size_arg = 'f';
+ break;
+ }
+
+ if (i == porrectus_long_episema_index && note->next
+ && is_episema_shown(note->next)) {
+ ambitus = compute_ambitus(note);
+ }
+
+ if (i - 1 != porrectus_long_episema_index || !note->previous
+ || !is_episema_shown(note->previous)) {
+ if (connect) {
+ const gregorio_glyph *next;
+ if (!note->next
+ && (!(next = gregorio_next_non_texverb_glyph(glyph))
+ || next->type != GRE_SPACE
+ || next->u.misc.unpitched.info.space
+ != SP_ZERO_WIDTH)) {
+ /* not followed by a zero-width space */
+ /* try to fuse from punctum inclinatum to nobar glyph */
+ fprintf(f,
+ "\\GreHEpisemaBridge{%d}{%d}{%d}{%s%s}{%s}{%d}%%\n",
+ pitch_value(height), hepisema_case,
+ get_punctum_inclinatum_to_nobar_space_case(glyph),
+ suppose_high_ledger_line(note),
+ suppose_low_ledger_line(note),
+ adj->nudge? adj->nudge : "", adj->vbasepos);
+ } else if (note->next
+ && (note->next->u.note.shape
+ == S_PUNCTUM_INCLINATUM_ASCENDENS
+ || note->next->u.note.shape
+ == S_PUNCTUM_INCLINATUM_DESCENDENS
+ || note->next->u.note.shape
+ == S_PUNCTUM_INCLINATUM_DEMINUTUS
+ || note->next->u.note.shape
+ == S_PUNCTUM_INCLINATUM_AUCTUS)) {
+ /* is a punctum inclinatum of some sort */
+ fprintf(f, "\\GreHEpisemaBridge{%d}{%d}{%d}{%s%s}{%s}{%d}%%\n",
+ pitch_value(height), hepisema_case,
+ get_punctum_inclinatum_space_case(note->next),
+ suppose_high_ledger_line(note),
+ suppose_low_ledger_line(note),
+ adj->nudge? adj->nudge : "", adj->vbasepos);
+ }
+ }
+ fprintf(f, "\\GreHEpisema{%d}{\\GreOCase%s}{%d}{%d}{%c}{%d}"
+ "{%s%s}{%s}{%d}%%\n", pitch_value(height),
+ note->gtex_offset_case, ambitus, hepisema_case, size_arg,
+ pitch_value(height), suppose_high_ledger_line(note),
+ suppose_low_ledger_line(note), adj->nudge? adj->nudge : "",
+ adj->vbasepos);
+ }
+ }
+}
+
+/**
+ * @brief A function that writes the good \c \\hepisema in GregorioTeX.
+ * @param i The position of the note in the glyph.
+ */
+static void gregoriotex_write_hepisema(FILE *const f,
+ const gregorio_note *const note, const int i, const gtex_type type,
+ const gregorio_glyph *const glyph)
+{
+ int porrectus_long_episema_index = -1;
+
+ gregorio_assert(note, gregoriotex_write_hepisema,
+ "called with NULL pointer", return);
+
+ switch (type) {
+ case T_PORRECTUS:
+ case T_PORRECTUS_FLEXUS:
+ porrectus_long_episema_index = 1;
+ break;
+ case T_TORCULUS_RESUPINUS:
+ case T_TORCULUS_RESUPINUS_FLEXUS:
+ porrectus_long_episema_index = 2;
+ break;
+ default:
+ /* do nothing */
+ break;
+ }
+
+ write_single_hepisema(f, 1, note, SO_UNDER, note->h_episema_below_connect,
+ note->h_episema_below, note->h_episema_below_size, i, glyph,
+ porrectus_long_episema_index, &gtex_is_h_episema_below_shown);
+ write_single_hepisema(f, 0, note, SO_OVER, note->h_episema_above_connect,
+ note->h_episema_above, note->h_episema_above_size, i, glyph,
+ porrectus_long_episema_index, &gtex_is_h_episema_above_shown);
+}
+
+/* a macro to write an additional line */
+
+static void write_additional_line(FILE *f, int i, gtex_type type, bool bottom,
+ const gregorio_note *current_note, const gregorio_score *const score)
+{
+ char ambitus = 0;
+ gregorio_assert(current_note, write_additional_line, "called with no note",
+ return);
+ /* patch to get a line under the full glyph in the case of dbc (for
+ * example) */
+ switch (type) {
+ case T_PORRECTUS:
+ case T_PORRECTUS_FLEXUS:
+ switch (i) {
+ case 1:
+ i = HEPISEMA_FIRST_TWO;
+ break;
+ case 2:
+ if (current_note->previous->u.note.pitch > LOW_LEDGER_LINE_PITCH
+ && current_note->previous->u.note.pitch
+ < score->high_ledger_line_pitch) {
+ i = HEPISEMA_FIRST_TWO;
+ /* HEPISEMA_FIRST_TWO works only for first note */
+ current_note = current_note->previous;
+ } else {
+ return;
+ }
+ break;
+ case 3:
+ if (bottom || current_note->previous->u.note.pitch
+ >= score->high_ledger_line_pitch) {
+ /* we don't need to add twice the same line */
+ return;
+ }
+ break;
+ }
+ break;
+ case T_TORCULUS_RESUPINUS:
+ case T_TORCULUS_RESUPINUS_FLEXUS:
+ switch (i) {
+ case 2:
+ i = HEPISEMA_FIRST_TWO;
+ break;
+ case 3:
+ if (current_note->previous->u.note.pitch > LOW_LEDGER_LINE_PITCH
+ && current_note->previous->u.note.pitch
+ < score->high_ledger_line_pitch) {
+ i = HEPISEMA_FIRST_TWO;
+ /* HEPISEMA_FIRST_TWO works only for first note */
+ current_note = current_note->previous;
+ } else {
+ return;
+ }
+ break;
+ case 4:
+ if (bottom || current_note->previous->u.note.pitch
+ >= score->high_ledger_line_pitch) {
+ /* we don't need to add twice the same line */
+ return;
+ }
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (i == HEPISEMA_FIRST_TWO) {
+ /* here we must compare the first note of the big bar with the second
+ * one, but it may be tricky sometimes, because of the previous patch */
+ gregorio_assert_only(!current_note->previous ||
+ current_note->u.note.pitch
+ > current_note->previous->u.note.pitch,
+ write_additional_line, "HEPISEMA_FIRST_TWO on the wrong note");
+ ambitus = current_note->u.note.pitch - current_note->next->u.note.pitch;
+ }
+ fprintf(f, "\\GreAdditionalLine{\\GreOCase%s}{%d}{%d}%%\n",
+ current_note->gtex_offset_case, ambitus, bottom ? 3 : 2);
+}
+
+/*
+ *
+ * a function that writes the good value of \vepisema in GregorioTeX. i is the
+ * position of the note in the glyph
+ *
+ */
+
+static void write_vepisema(FILE *f, const gregorio_note *const note)
+{
+ fprintf(f, "\\GreVEpisema{%d}{\\GreOCase%s}%%\n",
+ pitch_value(note->v_episema_height), note->gtex_offset_case);
+}
+
+/*
+ * a function that writes the rare signs in GregorioTeX. i is the position of
+ * the note in the glyph */
+
+static void write_rare_sign(FILE *f, const gregorio_note *const current_note,
+ const gregorio_sign rare)
+{
+ switch (rare) {
+ case _ACCENTUS:
+ fprintf(f, "\\GreAccentus{%d}{\\GreOCase%s}%%\n",
+ pitch_value(current_note->u.note.pitch),
+ current_note->gtex_offset_case);
+ break;
+ case _ACCENTUS_REVERSUS:
+ fprintf(f, "\\GreReversedAccentus{%d}{\\GreOCase%s}%%\n",
+ pitch_value(current_note->u.note.pitch),
+ current_note->gtex_offset_case);
+ break;
+ case _CIRCULUS:
+ fprintf(f, "\\GreCirculus{%d}{\\GreOCase%s}%%\n",
+ pitch_value(current_note->u.note.pitch),
+ current_note->gtex_offset_case);
+ break;
+ case _SEMI_CIRCULUS:
+ fprintf(f, "\\GreSemicirculus{%d}{\\GreOCase%s}%%\n",
+ pitch_value(current_note->u.note.pitch),
+ current_note->gtex_offset_case);
+ break;
+ case _SEMI_CIRCULUS_REVERSUS:
+ fprintf(f, "\\GreReversedSemicirculus{%d}{\\GreOCase%s}%%\n",
+ pitch_value(current_note->u.note.pitch),
+ current_note->gtex_offset_case);
+ break;
+ case _MUSICA_FICTA_FLAT:
+ fprintf(f, "\\GreMusicaFictaFlat{%d}{\\GreOCase%s}%%\n",
+ pitch_value(current_note->u.note.pitch),
+ current_note->gtex_offset_case);
+ break;
+ case _MUSICA_FICTA_NATURAL:
+ fprintf(f, "\\GreMusicaFictaNatural{%d}{\\GreOCase%s}%%\n",
+ pitch_value(current_note->u.note.pitch),
+ current_note->gtex_offset_case);
+ break;
+ case _MUSICA_FICTA_SHARP:
+ fprintf(f, "\\GreMusicaFictaSharp{%d}{\\GreOCase%s}%%\n",
+ pitch_value(current_note->u.note.pitch),
+ current_note->gtex_offset_case);
+ break;
+ /* the cases of the bar signs are dealt in another function
+ * (write_bar) */
+ default:
+ /* not reachable unless there's a programming error */
+ /* LCOV_EXCL_START */
+ assert(false);
+ break;
+ /* LCOV_EXCL_STOP */
+ }
+}
+
+static __inline const char *alteration_name(
+ const gregorio_shape next_note_alteration)
+{
+ switch (next_note_alteration) {
+ case S_FLAT:
+ return "Flat";
+ case S_SHARP:
+ return "Sharp";
+ case S_NATURAL:
+ return "Natural";
+ default:
+ return "";
+ }
+}
+
+static const char *next_custos(const signed char next_note_pitch,
+ const gregorio_shape next_note_alteration,
+ gregoriotex_status *const status)
+{
+ static char buf[50];
+
+ gregorio_snprintf(buf, sizeof buf, "%s\\GreNextCustos{%d}{%s}",
+ status->suppressed_custos? "\\GreResetEolCustos" : "",
+ pitch_value(next_note_pitch),
+ alteration_name(next_note_alteration));
+
+ status->suppressed_custos = false;
+
+ return buf;
+}
+
+/*
+ * function used when the glyph is only one note long
+ */
+
+static void write_note(FILE *f, gregorio_note *note,
+ const gregorio_glyph *glyph, const char next_note_pitch,
+ const gregorio_shape next_note_alteration,
+ gregoriotex_status *const status)
+{
+ unsigned int initial_shape = note->u.note.shape;
+ const char *shape;
+ int space_case;
+ static char cpbuf[96];
+ /* type in the sense of GregorioTeX alignment type */
+ gtex_alignment type = AT_ONE_NOTE;
+ gregorio_assert(note, write_note, "called with NULL pointer",
+ return);
+ if (note->u.note.shape == S_PUNCTUM) {
+ switch (note->u.note.liquescentia) {
+ case L_AUCTUS_ASCENDENS:
+ note->u.note.shape = S_PUNCTUM_AUCTUS_ASCENDENS;
+ break;
+ case L_AUCTUS_DESCENDENS:
+ note->u.note.shape = S_PUNCTUM_AUCTUS_DESCENDENS;
+ break;
+ case L_INITIO_DEBILIS:
+ if (glyph->u.notes.fuse_to_next_glyph > 0) {
+ break;
+ }
+ /* else fall through to next case */
+ case L_DEMINUTUS:
+ /* this is a currenly unused, but we keep it as a fallback case */
+ /* LCOV_EXCL_START */
+ note->u.note.shape = S_PUNCTUM_DEMINUTUS;
+ break;
+ /* LCOV_EXCL_STOP */
+ default:
+ break;
+ }
+ }
+ shape = determine_note_glyph_name(note, glyph, &type);
+ note->u.note.shape = initial_shape;
+ /* special things for puncta inclinata */
+ space_case = get_punctum_inclinatum_space_case(note);
+ if (space_case >= 0) {
+ fprintf(f, "\\GreEndOfGlyph{%d}%%\n", space_case);
+ }
+
+ switch (note->u.note.shape) {
+ case S_FLAT:
+ fprintf(f, "\\GreFlat{%d}{0}", pitch_value(note->u.note.pitch));
+ break;
+ case S_NATURAL:
+ fprintf(f, "\\GreNatural{%d}{0}", pitch_value(note->u.note.pitch));
+ break;
+ case S_SHARP:
+ fprintf(f, "\\GreSharp{%d}{0}", pitch_value(note->u.note.pitch));
+ break;
+ default:
+ fprintf(f, "\\GreGlyph{%s}{%d}{%s}{%d}",
+ code_point(shape, glyph->u.notes.is_cavum, cpbuf, sizeof cpbuf),
+ pitch_value(note->u.note.pitch),
+ next_custos(next_note_pitch, next_note_alteration, status),
+ type);
+ break;
+ }
+}
+
+static int gregoriotex_syllable_first_type(gregorio_syllable *syllable)
+{
+ int result = 0;
+ gtex_alignment type = AT_ONE_NOTE;
+ gtex_type gtype = T_ONE_NOTE;
+ /* alteration says if there is a flat or a natural first in the next
+ * syllable, see gregoriotex.tex for more details */
+ int alteration = 0;
+ gregorio_glyph *glyph;
+ gregorio_element *element;
+ gregorio_assert(syllable && syllable->elements,
+ gregoriotex_syllable_first_type, "called with a NULL argument",
+ return 0);
+ for (element = syllable->elements[0]; element; element = element->next) {
+ if (element->type == GRE_BAR) {
+ switch (element->u.misc.unpitched.info.bar) {
+ case B_NO_BAR:
+ case B_VIRGULA:
+ case B_VIRGULA_HIGH:
+ result = 10;
+ break;
+ case B_DIVISIO_MINIMIS:
+ case B_DIVISIO_MINIMIS_HIGH:
+ case B_DIVISIO_MINIMA:
+ case B_DIVISIO_MINIMA_HIGH:
+ result = 11;
+ break;
+ case B_DIVISIO_MINOR:
+ result = 12;
+ break;
+ case B_DIVISIO_MAIOR:
+ case B_DIVISIO_MAIOR_DOTTED:
+ result = 13;
+ break;
+ case B_DIVISIO_MINOR_D1:
+ case B_DIVISIO_MINOR_D2:
+ case B_DIVISIO_MINOR_D3:
+ case B_DIVISIO_MINOR_D4:
+ case B_DIVISIO_MINOR_D5:
+ case B_DIVISIO_MINOR_D6:
+ case B_DIVISIO_MINOR_D7:
+ case B_DIVISIO_MINOR_D8:
+ result = 14;
+ break;
+ case B_DIVISIO_FINALIS:
+ result = 15;
+ break;
+ default:
+ /* not reachable unless there's a programming error */
+ /* LCOV_EXCL_START */
+ gregorio_fail(gregoriotex_syllable_first_type, "invalid bar");
+ result = 0;
+ break;
+ /* LCOV_EXCL_STOP */
+ }
+ return result;
+ }
+ if (element->type == GRE_ELEMENT && element->u.first_glyph) {
+ for (glyph = element->u.first_glyph; glyph; glyph = glyph->next) {
+ if (glyph->type == GRE_GLYPH && glyph->u.notes.first_note) {
+ switch (glyph->u.notes.glyph_type) {
+ case G_ALTERATION:
+ if (alteration == 0) {
+ switch (glyph->u.notes.first_note->u.note.shape) {
+ case S_FLAT:
+ alteration = 20;
+ break;
+ case S_NATURAL:
+ alteration = 40;
+ break;
+ case S_SHARP:
+ alteration = 60;
+ break;
+ default:
+ /* not reachable unless there's a programming error */
+ /* LCOV_EXCL_START */
+ gregorio_fail(gregoriotex_syllable_first_type,
+ "invalid alteration");
+ break;
+ /* LCOV_EXCL_STOP */
+ }
+ }
+ continue;
+ case G_PUNCTA_INCLINATA:
+ case G_2_PUNCTA_INCLINATA_DESCENDENS:
+ case G_3_PUNCTA_INCLINATA_DESCENDENS:
+ case G_4_PUNCTA_INCLINATA_DESCENDENS:
+ case G_5_PUNCTA_INCLINATA_DESCENDENS:
+ case G_2_PUNCTA_INCLINATA_ASCENDENS:
+ case G_3_PUNCTA_INCLINATA_ASCENDENS:
+ case G_4_PUNCTA_INCLINATA_ASCENDENS:
+ case G_5_PUNCTA_INCLINATA_ASCENDENS:
+ case G_PUNCTUM:
+ case G_STROPHA:
+ case G_VIRGA:
+ case G_VIRGA_REVERSA:
+ case G_STROPHA_AUCTA:
+ case G_DISTROPHA:
+ case G_DISTROPHA_AUCTA:
+ case G_TRISTROPHA:
+ case G_TRISTROPHA_AUCTA:
+ case G_BIVIRGA:
+ case G_TRIVIRGA:
+ determine_note_glyph_name
+ (glyph->u.notes.first_note, glyph, &type);
+ break;
+ default:
+ gregoriotex_determine_glyph_name(glyph, &type, &gtype);
+ break;
+ }
+ return type + alteration;
+ }
+ }
+ }
+ }
+ if (syllable->elements[0]) {
+ /* if there is anything else in the next syllable */
+ return 0;
+ }
+ /* the only thing that should reach this point is an empty next syllable
+ * we treat that kind of syllable as a bar */
+ return 16;
+}
+
+static __inline void write_low_choral_sign(FILE *const f,
+ const gregorio_note *const note, int special)
+{
+ if (note->choral_sign_is_nabc) {
+ fprintf(f, "\\GreLowChoralSign{%d}{\\GreNABCChar{",
+ pitch_value(note->u.note.pitch));
+ tex_escape_text(f, note->choral_sign);
+ fprintf(f, "}}{%d}%%\n", special);
+ } else {
+ fprintf(f, "\\GreLowChoralSign{%d}{%s}{%d}%%\n",
+ pitch_value(note->u.note.pitch), note->choral_sign,
+ special);
+ }
+}
+
+static __inline void write_high_choral_sign(FILE *const f,
+ const gregorio_note *const note, int pitch_offset)
+{
+ if (note->choral_sign_is_nabc) {
+ fprintf(f, "\\GreHighChoralSign{%d}{\\GreNABCChar{",
+ pitch_value(note->u.note.pitch + pitch_offset));
+ tex_escape_text(f, note->choral_sign);
+ fprintf(f, "}}{\\GreOCase%s}%%\n", note->gtex_offset_case);
+ } else {
+ fprintf(f, "\\GreHighChoralSign{%d}{%s}{\\GreOCase%s}%%\n",
+ pitch_value(note->u.note.pitch + pitch_offset),
+ note->choral_sign, note->gtex_offset_case);
+ }
+}
+
+static void write_choral_sign(FILE *f, const gregorio_glyph *glyph,
+ const gregorio_note *current_note, bool low)
+{
+ bool kind_of_pes;
+ /* false in the normal case (sign above the note), true in the case of it's
+ * next to the note (same height as a punctum) */
+ bool low_sign = choral_sign_here_is_low(glyph, current_note, &kind_of_pes);
+
+ /* the low choral signs must be typeset after the punctum, whereas the high
+ * must be typeset before the h episema */
+ if ((low_sign && !low) || (!low_sign && low)) {
+ return;
+ }
+
+ if (low_sign) {
+ /* very approximative heuristic, some things may have to be adapted
+ * here... */
+ if (is_on_a_line(current_note->u.note.pitch)) {
+ if (kind_of_pes && current_note->u.note.pitch -
+ current_note->next->u.note.pitch == -1) {
+ write_low_choral_sign(f, current_note, 1);
+ return;
+ }
+ if (current_note->previous
+ && (current_note->previous->signs == _PUNCTUM_MORA
+ || current_note->previous->signs ==
+ _V_EPISEMA_PUNCTUM_MORA)) {
+ write_low_choral_sign(f, current_note, 1);
+ return;
+ }
+ }
+
+ write_low_choral_sign(f, current_note, 0);
+ } else {
+ /* let's cheat a little */
+ if (is_on_a_line(current_note->u.note.pitch)) {
+ write_high_choral_sign(f, current_note, 0);
+ } else {
+ write_high_choral_sign(f, current_note, 2);
+ }
+ }
+}
+
+/*
+ *
+ * A function that write the signs of a glyph, which has the type type (T_*,
+ * not G_*, which is in the glyph->glyph_type), and (important), we start only
+ * at the note current_note. It is due to the way we call it : if type is
+ * T_ONE_NOTE, we just do the signs on current_note, not all. This is the case
+ * for example for the first note of the torculus resupinus, or the
+ * G_*_PUNCTA_INCLINATA.
+ *
+ */
+
+/* small helper */
+static __inline bool _found(FILE *const f, const bool found)
+{
+ if (!found) {
+ fprintf (f, "%%\n");\
+ return true;
+ }
+ return found;
+}
+
+static void compute_height_extrema(const gregorio_glyph *const glyph,
+ const gregorio_note *note, signed char *const top_height,
+ signed char *const bottom_height)
+{
+ char height;
+ /* get the minima/maxima pitches */
+ for (; note; note = note->next) {
+ if (note->h_episema_above) {
+ height = note->h_episema_above;
+ } else {
+ if (note->v_episema_height && note->v_episema_height
+ > note->u.note.pitch) {
+ height = note->v_episema_height;
+ } else {
+ height = note->u.note.pitch;
+ }
+
+ if (note->choral_sign
+ && !choral_sign_here_is_low(glyph, note, NULL)) {
+ ++height;
+ }
+ }
+ if (*top_height == UNDETERMINED_HEIGHT || height > *top_height) {
+ *top_height = height;
+ }
+
+ if (note->h_episema_below) {
+ height = note->h_episema_below;
+ } else if (note->v_episema_height && note->v_episema_height
+ < note->u.note.pitch) {
+ height = note->v_episema_height;
+ } else {
+ height = note->u.note.pitch;
+
+ /* no vertical episema, so we need to check for a stem */
+ gregorio_assert(glyph->type == GRE_GLYPH, compute_height_extrema,
+ "glyph->type must be GRE_GLYPH here", return);
+ switch (glyph->u.notes.glyph_type) {
+ case G_BIVIRGA:
+ case G_TRIVIRGA:
+ case G_VIRGA:
+ case G_VIRGA_REVERSA:
+ --height;
+ break;
+
+ case G_PUNCTUM:
+ switch (note->u.note.shape) {
+ case S_ORISCUS_SCAPUS_ASCENDENS:
+ case S_ORISCUS_SCAPUS_DESCENDENS:
+ --height;
+ break;
+
+ default:
+ /* default to avoid the warning */
+ break;
+ }
+ break;
+
+ default:
+ /* default to avoid the warning */
+
+ /* other shapes like the porrectus have stems, but the height
+ * computation will cover other notes in the shape, negating
+ * the need to account for the stem on the shape */
+ break;
+ }
+ }
+
+ if (*bottom_height == UNDETERMINED_HEIGHT || height < *bottom_height) {
+ *bottom_height = height;
+ }
+ }
+}
+
+static void compute_element_height_extrema(
+ const gregorio_element *const element, signed char *const top_height,
+ signed char *const bottom_height)
+{
+ const gregorio_glyph *glyph;
+ gregorio_not_null(element, compute_element_height_extrema, return);
+ if (element->type != GRE_ELEMENT) {
+ return;
+ }
+ /* get the minima/maxima pitches */
+ for (glyph = element->u.first_glyph; glyph; glyph = glyph->next) {
+ if (glyph->type == GRE_GLYPH) {
+ compute_height_extrema(glyph, glyph->u.notes.first_note,
+ top_height, bottom_height);
+ }
+ }
+}
+
+static __inline void fixup_height_extrema(signed char *const top_height,
+ signed char *const bottom_height)
+{
+ if (*top_height == UNDETERMINED_HEIGHT) {
+ *top_height = 9; /* 'g' */
+ }
+ if (*bottom_height == UNDETERMINED_HEIGHT) {
+ *bottom_height = 9; /* 'g' */
+ }
+}
+
+static __inline bool is_ledger_drawn(const bool setting,
+ const gregorio_ledger_specificity specificity)
+{
+ return ((specificity & LEDGER_DRAWN) && setting);
+}
+
+static void write_signs(FILE *f, gtex_type type,
+ const gregorio_glyph *glyph, const gregorio_note *note,
+ int fuse_to_next_note, gregoriotex_status *const status,
+ const gregorio_score *const score)
+{
+ /* i is the number of the note for which we are typesetting the sign. */
+ int i;
+ const gregorio_note *current_note;
+ signed char high_pitch = UNDETERMINED_HEIGHT;
+ signed char low_pitch = UNDETERMINED_HEIGHT;
+ bool found = false;
+ compute_height_extrema(glyph, note, &high_pitch, &low_pitch);
+ fixup_height_extrema(&high_pitch, &low_pitch);
+ fprintf(f, "%%\n{%%\n\\GreGlyphHeights{%d}{%d}%%\n",
+ pitch_value(high_pitch), pitch_value(low_pitch));
+ for (current_note = note, i = 1; current_note;
+ current_note = current_note->next, ++i) {
+ /* we start by the additional lines */
+ if (is_ledger_drawn(current_note->low_ledger_line,
+ current_note->low_ledger_specificity)) {
+ write_additional_line(f, i, type, true, current_note, score);
+ status->bottom_line = 1;
+ }
+ if (is_ledger_drawn(current_note->high_ledger_line,
+ current_note->high_ledger_specificity)) {
+ write_additional_line(f, i, type, false, current_note, score);
+ }
+ if (current_note->texverb) {
+ fprintf(f, "%% verbatim text at note level:\n%s%%\n"
+ "%% end of verbatim text\n",
+ gregorio_texverb(current_note->texverb));
+ }
+ if (type == T_ONE_NOTE) {
+ break;
+ }
+ }
+ fprintf(f, "}{");
+ /* now a first loop for the choral signs, because high signs must be taken
+ * into account before any hepisema */
+ for (current_note = note, i = 1; current_note;
+ current_note = current_note->next, ++i) {
+ if (current_note->choral_sign) {
+ found = _found(f, found);
+ write_choral_sign(f, glyph, current_note, false);
+ }
+ if (type == T_ONE_NOTE) {
+ break;
+ }
+ }
+ /* a loop for rare signs, vertical episema, and horizontal episema */
+ for (current_note = note, i = 1; current_note;
+ current_note = current_note->next, ++i) {
+ /* we continue with the hepisema */
+ if (current_note->h_episema_above || current_note->h_episema_below) {
+ found = _found(f, found);
+ gregoriotex_write_hepisema(f, current_note, i, type, glyph);
+ }
+ /* write_rare_sign also writes the vepisema */
+ if (current_note->special_sign) {
+ found = _found(f, found);
+ write_rare_sign(f, current_note, current_note->special_sign);
+ }
+ if (current_note->signs != _NO_SIGN) {
+ found = _found(f, found);
+ }
+ switch (current_note->signs) {
+ case _V_EPISEMA:
+ case _V_EPISEMA_PUNCTUM_MORA:
+ case _V_EPISEMA_AUCTUM_DUPLEX:
+ write_vepisema(f, current_note);
+ break;
+ default:
+ /* do nothing */
+ break;
+ }
+ if (type == T_ONE_NOTE) {
+ break;
+ }
+ }
+ /* final loop for choral signs and punctum mora */
+ for (current_note = note, i = 1; current_note;
+ current_note = current_note->next, ++i) {
+ switch (current_note->signs) {
+ case _PUNCTUM_MORA:
+ case _V_EPISEMA_PUNCTUM_MORA:
+ write_punctum_mora(f, glyph, current_note, fuse_to_next_note);
+ break;
+ case _AUCTUM_DUPLEX:
+ case _V_EPISEMA_AUCTUM_DUPLEX:
+ write_auctum_duplex(f, current_note);
+ break;
+ default:
+ break;
+ }
+ if (current_note->choral_sign) {
+ found = _found(f, found);
+ write_choral_sign(f, glyph, current_note, true);
+ }
+ if (type == T_ONE_NOTE) {
+ break;
+ }
+ }
+ fprintf(f, "}{");
+ if (status->point_and_click) {
+ fprintf(f, "%u:%u:%u", note->src_line, note->src_offset,
+ note->src_column + 1);
+ }
+ fprintf(f, "}%%\n");
+}
+
+static char *determine_leading_shape(const gregorio_glyph *const glyph)
+{
+ static char buf[BUFSIZE];
+ int ambitus = compute_ambitus(glyph->u.notes.first_note);
+ const char *head, *head_liquescence;
+
+ switch (glyph->u.notes.first_note->u.note.shape) {
+ case S_QUILISMA:
+ head = "Quilisma";
+ break;
+ case S_ORISCUS_ASCENDENS:
+ case S_ORISCUS_DESCENDENS:
+ head = "Oriscus";
+ break;
+ default:
+ head = "Punctum";
+ break;
+ }
+
+ if (glyph->u.notes.liquescentia & L_INITIO_DEBILIS) {
+ head_liquescence = "InitioDebilis";
+ } else {
+ head_liquescence = "";
+ }
+
+ gregorio_snprintf(buf, BUFSIZE, "Leading%s%s%s", head, tex_ambitus[ambitus],
+ head_liquescence);
+ return buf;
+}
+
+static __inline void write_composed_multinote_glyph(FILE *const f,
+ gregorio_note *current_note, const gregorio_glyph *glyph,
+ const signed char next_note_pitch,
+ const gregorio_shape next_note_alteration,
+ const int fuse_to_next_note, gregoriotex_status *const status,
+ const gregorio_score *const score, const char *glyph_end_between_notes)
+{
+ while (current_note) {
+ const bool has_next = current_note->next != NULL;
+ if (has_next) {
+ write_note(f, current_note, glyph, current_note->next->u.note.pitch,
+ S_UNDETERMINED, status);
+ } else {
+ write_note(f, current_note, glyph, next_note_pitch,
+ next_note_alteration, status);
+ }
+ write_signs(f, T_ONE_NOTE, glyph, current_note,
+ has_next ? 0 : fuse_to_next_note, status, score);
+ current_note = current_note->next;
+ if (current_note && glyph_end_between_notes) {
+ fputs(glyph_end_between_notes, f);
+ }
+ }
+}
+
+static void write_glyph(FILE *const f, const gregorio_syllable *const syllable,
+ const gregorio_element *const element,
+ gregorio_glyph *const glyph,
+ gregoriotex_status *const status, const gregorio_score *const score)
+{
+ static char cpbuf[96], cpbuf2[96];
+ /* type is the type of the glyph. Understand the type of the glyph for
+ * gregoriotex, for the alignement between text and notes. (AT_ONE_NOTE,
+ * etc.) */
+ gtex_alignment type = 0;
+ /* the type of the glyph, in the sense of the shape (T_PES, etc.) */
+ gtex_type gtype = 0;
+ signed char next_note_pitch = 0;
+ gregorio_note *current_note;
+ const char *leading_shape, *shape;
+ const gregorio_glyph *prev_glyph =
+ gregorio_previous_non_texverb_glyph(glyph);
+ int fuse_to_next_note, fuse_from_previous_note =
+ (prev_glyph && prev_glyph->type == GRE_GLYPH)
+ ? prev_glyph->u.notes.fuse_to_next_glyph : 0;
+ gregorio_shape next_note_alteration;
+ gregorio_assert(glyph, write_glyph, "called with NULL pointer", return);
+ gregorio_assert(glyph->type == GRE_GLYPH && glyph->u.notes.first_note,
+ write_glyph, "called with glyph without note", return);
+ fuse_to_next_note = glyph->u.notes.fuse_to_next_glyph;
+ if (fuse_from_previous_note) {
+ fprintf(f, "\\GreFuse");
+ }
+ next_note_pitch = gregorio_determine_next_pitch(syllable, element, glyph,
+ &next_note_alteration);
+ current_note = glyph->u.notes.first_note;
+ /* first we check if it is really a unique glyph in gregoriotex... the
+ * glyphs that are not a unique glyph are : trigonus and pucta inclinata
+ * in general, and torculus resupinus and torculus resupinus flexus, so
+ * we first divide the glyph into real gregoriotex glyphs */
+ switch (glyph->u.notes.glyph_type) {
+ case G_PUNCTA_INCLINATA:
+ case G_2_PUNCTA_INCLINATA_DESCENDENS:
+ case G_3_PUNCTA_INCLINATA_DESCENDENS:
+ case G_4_PUNCTA_INCLINATA_DESCENDENS:
+ case G_5_PUNCTA_INCLINATA_DESCENDENS:
+ case G_2_PUNCTA_INCLINATA_ASCENDENS:
+ case G_3_PUNCTA_INCLINATA_ASCENDENS:
+ case G_4_PUNCTA_INCLINATA_ASCENDENS:
+ case G_5_PUNCTA_INCLINATA_ASCENDENS:
+ write_composed_multinote_glyph(f, current_note, glyph,
+ next_note_pitch, next_note_alteration, fuse_to_next_note,
+ status, score, NULL);
+ break;
+ case G_SCANDICUS:
+ if ((glyph->u.notes.liquescentia & L_DEMINUTUS)
+ || glyph->u.notes.liquescentia == L_NO_LIQUESCENTIA
+ || glyph->u.notes.liquescentia == L_FUSED) {
+ shape = gregoriotex_determine_glyph_name(glyph, &type, &gtype);
+ fprintf(f, "\\GreGlyph{%s}{%d}{%s}{%d}", code_point(shape,
+ glyph->u.notes.is_cavum, cpbuf, sizeof cpbuf),
+ pitch_value(glyph->u.notes.first_note->u.note.pitch),
+ next_custos(next_note_pitch, next_note_alteration, status),
+ type);
+ write_signs(f, gtype, glyph, glyph->u.notes.first_note,
+ fuse_to_next_note, status, score);
+ } else {
+ write_composed_multinote_glyph(f, current_note, glyph,
+ next_note_pitch, next_note_alteration, fuse_to_next_note,
+ status, score, NULL);
+ }
+ break;
+ case G_ANCUS:
+ gregorio_assert(glyph->u.notes.liquescentia & L_DEMINUTUS,
+ write_glyph, "encountered a non-deminutus ancus", break);
+ shape = gregoriotex_determine_glyph_name(glyph, &type, &gtype);
+ fprintf(f, "\\GreGlyph{%s}{%d}{%s}{%d}", code_point(shape,
+ glyph->u.notes.is_cavum, cpbuf, sizeof cpbuf),
+ pitch_value(glyph->u.notes.first_note->u.note.pitch),
+ next_custos(next_note_pitch, next_note_alteration, status),
+ type);
+ write_signs(f, gtype, glyph, glyph->u.notes.first_note,
+ fuse_to_next_note, status, score);
+ break;
+ case G_TORCULUS_RESUPINUS_FLEXUS:
+ /* we retain this "old-style" fusion as it does look marginally better
+ * on screen */
+ leading_shape = determine_leading_shape(glyph);
+ /* trick to have the good position for these glyphs */
+ glyph->u.notes.glyph_type = G_PORRECTUS_FLEXUS_NO_BAR;
+ glyph->u.notes.first_note = current_note->next;
+ shape = gregoriotex_determine_glyph_name(glyph, &type, &gtype);
+ fprintf(f, "\\GreGlyph{\\GreFuseTwo{%s}{%s}}{%d}{%s}{%d}",
+ code_point(leading_shape, glyph->u.notes.is_cavum, cpbuf,
+ sizeof cpbuf),
+ code_point(shape, glyph->u.notes.is_cavum, cpbuf2,
+ sizeof cpbuf2),
+ pitch_value(glyph->u.notes.first_note->u.note.pitch),
+ next_custos(next_note_pitch, next_note_alteration, status),
+ type);
+ glyph->u.notes.first_note = current_note;
+ glyph->u.notes.glyph_type = G_TORCULUS_RESUPINUS_FLEXUS;
+ write_signs(f, gtype, glyph, glyph->u.notes.first_note,
+ fuse_to_next_note, status, score);
+ break;
+ case G_BIVIRGA:
+ case G_TRIVIRGA:
+ write_composed_multinote_glyph(f, current_note, glyph,
+ next_note_pitch, next_note_alteration, fuse_to_next_note,
+ status, score, "\\GreEndOfGlyph{4}%%\n");
+ break;
+ case G_TRISTROPHA:
+ case G_TRISTROPHA_AUCTA:
+ case G_DISTROPHA:
+ case G_DISTROPHA_AUCTA:
+ write_composed_multinote_glyph(f, current_note, glyph,
+ next_note_pitch, next_note_alteration, fuse_to_next_note,
+ status, score, "\\GreEndOfGlyph{5}%%\n");
+ break;
+
+ case G_PUNCTUM:
+ switch (glyph->u.notes.first_note->u.note.shape) {
+ case S_ORISCUS_ASCENDENS:
+ case S_ORISCUS_DESCENDENS:
+ case S_ORISCUS_DEMINUTUS:
+ case S_ORISCUS_SCAPUS_ASCENDENS:
+ case S_ORISCUS_SCAPUS_DESCENDENS:
+ /* don't change the oriscus */
+ break;
+
+ default:
+ switch (glyph->u.notes.liquescentia) {
+ case L_AUCTUS_ASCENDENS:
+ glyph->u.notes.first_note->u.note.shape =
+ S_PUNCTUM_AUCTUS_ASCENDENS;
+ break;
+ case L_AUCTUS_DESCENDENS:
+ glyph->u.notes.first_note->u.note.shape =
+ S_PUNCTUM_AUCTUS_DESCENDENS;
+ break;
+ case L_INITIO_DEBILIS:
+ if (glyph->u.notes.fuse_to_next_glyph > 0) {
+ break;
+ }
+ /* else fall through to next case */
+ case L_DEMINUTUS:
+ glyph->u.notes.first_note->u.note.shape = S_PUNCTUM_DEMINUTUS;
+ default:
+ break;
+ }
+ break;
+ }
+
+ /* fall into the next case */
+ case G_PUNCTUM_INCLINATUM:
+ case G_VIRGA:
+ case G_VIRGA_REVERSA:
+ case G_STROPHA:
+ case G_STROPHA_AUCTA:
+ case G_ALTERATION:
+ write_note(f, glyph->u.notes.first_note, glyph, next_note_pitch,
+ next_note_alteration, status);
+ write_signs(f, T_ONE_NOTE, glyph, current_note, fuse_to_next_note,
+ status, score);
+ break;
+ default:
+ /* we retain this "old-style" fusion as it does look marginally better
+ * on screen */
+ /* special case of the torculus resupinus which first note is not a
+ * punctum */
+ if (glyph->u.notes.glyph_type == G_TORCULUS_RESUPINUS
+ && current_note->u.note.shape != S_PUNCTUM
+ && current_note->u.note.shape != S_QUILISMA) {
+ leading_shape = determine_leading_shape(glyph);
+ /* trick to have the good position for these glyphs */
+ glyph->u.notes.glyph_type = G_PORRECTUS_NO_BAR;
+ glyph->u.notes.first_note = current_note->next;
+ shape = gregoriotex_determine_glyph_name(glyph, &type, &gtype);
+ fprintf(f, "\\GreGlyph{\\GreFuseTwo{%s}{%s}}{%d}{%s}{%d}",
+ code_point(leading_shape, glyph->u.notes.is_cavum, cpbuf,
+ sizeof cpbuf),
+ code_point(shape, glyph->u.notes.is_cavum, cpbuf2,
+ sizeof cpbuf2),
+ pitch_value(glyph->u.notes.first_note->u.note.pitch),
+ next_custos(next_note_pitch, next_note_alteration, status),
+ type);
+ glyph->u.notes.first_note = current_note;
+ glyph->u.notes.glyph_type = G_TORCULUS_RESUPINUS;
+ write_signs(f, gtype, glyph, glyph->u.notes.first_note,
+ fuse_to_next_note, status, score);
+ break;
+ } else {
+ shape = gregoriotex_determine_glyph_name(glyph, &type, &gtype);
+ fprintf(f, "\\GreGlyph{%s}{%d}{%s}{%d}", code_point(shape,
+ glyph->u.notes.is_cavum, cpbuf, sizeof cpbuf),
+ pitch_value(glyph->u.notes.first_note->u.note.pitch),
+ next_custos(next_note_pitch, next_note_alteration, status),
+ type);
+ write_signs(f, gtype, glyph, glyph->u.notes.first_note,
+ fuse_to_next_note, status, score);
+ break;
+ }
+ }
+}
+
+static __inline unsigned int glyph_note_units(const gregorio_glyph *glyph)
+{
+ unsigned int count = 0;
+ const gregorio_note *note;
+ if (glyph->u.notes.glyph_type != G_ALTERATION) {
+ for (note = glyph->u.notes.first_note; note; note = note->next) {
+ ++count;
+ }
+ }
+ return count;
+}
+
+/* here we absolutely need to pass the syllable as an argument, because we
+ * will need the next note, that may be contained in the next syllable */
+
+static unsigned int write_element(FILE *const f,
+ const gregorio_syllable *const syllable,
+ const gregorio_element *const element,
+ gregoriotex_status *const status,
+ const gregorio_score *const score)
+{
+ unsigned int note_unit_count = 0;
+ if (element->type == GRE_ELEMENT) {
+ gregorio_glyph *glyph;
+ for (glyph = element->u.first_glyph; glyph; glyph = glyph->next) {
+ switch (glyph->type) {
+ case GRE_SPACE:
+ switch (glyph->u.misc.unpitched.info.space) {
+ case SP_ZERO_WIDTH:
+ fprintf(f, "\\GreEndOfGlyph{1}%%\n");
+ break;
+ case SP_HALF_SPACE:
+ fprintf(f, "\\GreEndOfGlyph{22}%%\n");
+ break;
+ case SP_INTERGLYPH_SPACE:
+ fprintf(f, "\\GreEndOfGlyph{0}%%\n");
+ break;
+ default:
+ /* not reachable unless there's a programming error */
+ /* LCOV_EXCL_START */
+ gregorio_fail(write_element,
+ "encountered an unexpected glyph-level space");
+ break;
+ /* LCOV_EXCL_STOP */
+ }
+ break;
+
+ case GRE_TEXVERB_GLYPH:
+ if (glyph->texverb) {
+ fprintf(f, "%% verbatim text at glyph level:\n%s%%\n"
+ "%% end of verbatim text\n",
+ gregorio_texverb(glyph->texverb));
+ }
+ break;
+
+ default:
+ /* at this point glyph->type is GRE_GLYPH */
+ assert(glyph->type == GRE_GLYPH);
+ write_glyph(f, syllable, element, glyph, status, score);
+ note_unit_count += glyph_note_units(glyph);
+ if (glyph->next && glyph->next->type == GRE_GLYPH &&
+ glyph->next->u.notes.glyph_type != G_ALTERATION) {
+ if (is_fused(glyph->next->u.notes.liquescentia)) {
+ int space_case =
+ get_punctum_inclinatum_to_nobar_space_case(glyph);
+ if (space_case >= 0) {
+ /* fuse from punctum inclinatum to nobar glyph */
+ fprintf(f, "\\GreEndOfGlyph{%d}%%\n", space_case);
+ } else {
+ fprintf(f, "\\GreEndOfGlyph{1}%%\n");
+ }
+ } else if (is_puncta_inclinata(
+ glyph->next->u.notes.glyph_type)
+ || glyph->next->u.notes.glyph_type
+ == G_PUNCTA_INCLINATA) {
+ if ((is_puncta_inclinata(glyph->u.notes.glyph_type)
+ || glyph->u.notes.glyph_type
+ == G_PUNCTA_INCLINATA)
+ && glyph->next->u.notes.first_note->u.note.pitch
+ == gregorio_glyph_last_note(glyph)->u.note.pitch) {
+ /* special case for unison puncta inclinata */
+ fprintf(f, "\\GreEndOfGlyph{23}%%\n");
+ } else {
+ fprintf(f, "\\GreEndOfGlyph{9}%%\n");
+ }
+ } else if (glyph->u.notes.glyph_type != G_ALTERATION
+ || !glyph->next) {
+ fprintf(f, "\\GreEndOfGlyph{0}%%\n");
+ }
+ }
+ break;
+ }
+ }
+ }
+ return note_unit_count;
+}
+
+static void write_fixed_text_styles(FILE *f, gregorio_character *syllable_text,
+ gregorio_character *next_syllable_text)
+{
+ if (syllable_text) {
+ gregoriotex_ignore_style = gregoriotex_fix_style(syllable_text);
+ if (gregoriotex_ignore_style) {
+ fprintf(f, "\\GreSetFixedTextFormat{%d}",
+ gregoriotex_internal_style_to_gregoriotex
+ (gregoriotex_ignore_style));
+ }
+ }
+ if (next_syllable_text) {
+ gregoriotex_next_ignore_style = gregoriotex_fix_style(
+ next_syllable_text);
+ if (gregoriotex_next_ignore_style) {
+ fprintf(f, "\\GreSetFixedNextTextFormat{%d}",
+ gregoriotex_internal_style_to_gregoriotex
+ (gregoriotex_next_ignore_style));
+ }
+ }
+}
+
+static void write_text(FILE *const f, const gregorio_character *const text)
+{
+ if (text == NULL) {
+ fprintf(f, "{}{}{}{}{}");
+ return;
+ }
+ fprintf(f, "{");
+ gregorio_write_text(WTP_NORMAL, text, f, &gtex_write_verb,
+ &gtex_print_char, &gtex_write_begin, &gtex_write_end,
+ &gtex_write_special_char);
+ fprintf(f, "}{");
+ gregorio_write_first_letter_alignment_text(WTP_NORMAL, text,
+ f, &gtex_write_verb, &gtex_print_char, &gtex_write_begin,
+ &gtex_write_end, &gtex_write_special_char);
+ gregoriotex_ignore_style = gregoriotex_next_ignore_style;
+ gregoriotex_next_ignore_style = ST_NO_STYLE;
+ fprintf(f, "}");
+}
+
+/*
+ * Function printing the line clef change (only updating \localleftbox, not
+ * printing the key). Useful for \GreDiscretionary.
+ * TODO: I'm not sure about the third argument, but that's how it's called in
+ * \GreChangeClef.
+ */
+static void gregoriotex_print_change_line_clef(FILE *f,
+ const gregorio_element *const current_element)
+{
+ if (current_element->type == GRE_CLEF) {
+ /* the third argument is 0 or 1 according to the need for a space
+ * before the clef */
+ fprintf(f, "\\GreSetLinesClef{%c}{%d}{1}{%d}{%c}{%d}{%d}%%\n",
+ gregorio_clef_to_char(current_element->u.misc.clef.clef),
+ current_element->u.misc.clef.line,
+ clef_flat_height(current_element->u.misc.clef.clef,
+ current_element->u.misc.clef.line,
+ current_element->u.misc.clef.flatted),
+ gregorio_clef_to_char(
+ current_element->u.misc.clef.secondary_clef),
+ current_element->u.misc.clef.secondary_line,
+ clef_flat_height(current_element->u.misc.clef.secondary_clef,
+ current_element->u.misc.clef.secondary_line,
+ current_element->u.misc.clef.secondary_flatted));
+ }
+}
+
+static __inline bool next_is_bar(const gregorio_syllable *syllable,
+ const gregorio_element *element)
+{
+ bool got_custos = false;
+ if (element) {
+ element = element->next;
+ }
+
+ while (syllable) {
+ if (element) {
+ if (element->type == GRE_BAR) {
+ return true;
+ }
+ /* allow no more than one manual custos before a bar */
+ if (got_custos || !is_manual_custos(element)) {
+ return false;
+ }
+ got_custos = true;
+ if (element->next) {
+ /* look at the next element */
+ element = element->next;
+ continue;
+ }
+ }
+
+ syllable = syllable->next_syllable;
+ if (!syllable) {
+ return false;
+ }
+
+ element = syllable->elements[0];
+ }
+
+ /* not reachable unless there's a programming error */
+ /* LCOV_EXCL_START */
+ gregorio_fail(next_is_bar, "unexpected end of syllables/elements");
+ return false;
+ /* LCOV_EXCL_STOP */
+}
+
+static void finish_syllable(FILE *f, gregorio_syllable *syllable) {
+ /* Very last, if the syllable is the end of a no-linebreak area: */
+ if (syllable->no_linebreak_area == NLBA_END) {
+ fprintf(f, "\\GreEndNLBArea{%d}{0}%%\n",
+ next_is_bar(syllable, NULL)? 3 : 1);
+ }
+ if (syllable->euouae == EUOUAE_END) {
+ fprintf(f, "\\GreEndEUOUAE{%d}%%\n",
+ next_is_bar(syllable, NULL)? 3 : 1);
+ }
+}
+
+static void handle_final_bar(FILE *f, const char *type, gregorio_syllable *syllable)
+{
+ gregorio_element *element;
+ fprintf(f, "\\GreFinal%s{%%\n", type);
+ /* first element will be the bar, which we just handled, so skip it */
+ for (element = (*syllable->elements)->next; element;
+ element = element->next) {
+ switch (element->type) {
+ case GRE_TEXVERB_ELEMENT:
+ if (element->texverb) {
+ fprintf(f, "%% verbatim text at element level:\n%s%%\n"
+ "%% end of verbatim text\n",
+ gregorio_texverb(element->texverb));
+ }
+ break;
+
+ case GRE_CUSTOS:
+ assert(element->u.misc.pitched.force_pitch);
+ fprintf(f, "\\GreFinalCustos{%d}{}%%\n",
+ pitch_value(element->u.misc.pitched.pitch));
+ break;
+
+ case GRE_END_OF_LINE:
+ fprintf(f, "\\GreFinalNewLine%%\n");
+ break;
+
+ default:
+ /* do nothing */
+ break;
+ }
+ }
+ fprintf(f, "}%%\n");
+ finish_syllable(f, syllable);
+}
+
+static __inline void write_syllable_point_and_click(FILE *const f,
+ const gregorio_syllable *const syllable,
+ const gregoriotex_status *const status)
+{
+ if (status->point_and_click && syllable->src_line) {
+ fprintf(f, "%u:%u:%u", syllable->src_line, syllable->src_offset,
+ syllable->src_column + 1);
+ }
+}
+
+static void write_syllable_text(FILE *f, const char *const syllable_type,
+ const gregorio_syllable *const syllable,
+ const bool ignored __attribute__((unused)))
+{
+ if (syllable_type != NULL) {
+ fprintf(f, "%s{%s%s\\GreSetThisSyllable", syllable_type,
+ syllable->clear? "\\GreClearSyllableText" : "",
+ syllable->forced_center? "\\GreGABCForceCenters" : "");
+ write_text(f, syllable->text);
+ fprintf(f, "}");
+ }
+}
+
+static void write_first_syllable_text(FILE *f, const char *const syllable_type,
+ const gregorio_syllable *const syllable,
+ const bool end_of_word)
+{
+ const gregorio_character *const text = syllable->text;
+ gregorio_not_null(syllable_type, write_first_syllable_text, return);
+ if (text == NULL) {
+ fprintf(f, "}{%s}{\\GreSetNoFirstSyllableText}", syllable_type);
+ } else {
+ gregorio_character *text_with_initial = gregorio_clone_characters(text),
+ *text_without_initial = gregorio_clone_characters(text);
+ const gregorio_character *t;
+
+ /* find out if there is a forced center -> has_forced_center */
+ gregorio_center_determination center = syllable->forced_center?
+ CENTER_FULLY_DETERMINED : CENTER_NOT_DETERMINED;
+
+ gregorio_rebuild_first_syllable(&text_with_initial, false);
+ gregorio_rebuild_characters(&text_with_initial, center, false);
+ gregorio_set_first_word(&text_with_initial);
+
+ gregorio_rebuild_first_syllable(&text_without_initial, true);
+ gregorio_rebuild_characters(&text_without_initial, center, true);
+ gregorio_set_first_word(&text_without_initial);
+
+ fprintf(f, "}{%s}{%s%s\\GreSetFirstSyllableText", syllable_type,
+ syllable->clear? "\\GreClearSyllableText" : "",
+ syllable->forced_center? "\\GreGABCForceCenters" : "");
+
+ fprintf(f, "{");
+ gregorio_write_first_letter_alignment_text(WTP_FIRST_SYLLABLE,
+ text_with_initial, f, &gtex_write_verb, &gtex_print_char,
+ &gtex_write_begin, &gtex_write_end, &gtex_write_special_char);
+ fprintf(f, "}{{");
+ gregorio_write_text(WTP_FIRST_SYLLABLE, text_without_initial, f,
+ &gtex_write_verb, &gtex_print_char, &gtex_write_begin,
+ &gtex_write_end, &gtex_write_special_char);
+ fprintf(f, "}}{{");
+ gregorio_write_text(WTP_NORMAL, text_with_initial, f, &gtex_write_verb,
+ &gtex_print_char, &gtex_write_begin, &gtex_write_end,
+ &gtex_write_special_char);
+ gregoriotex_ignore_style = gregoriotex_next_ignore_style;
+ gregoriotex_next_ignore_style = ST_NO_STYLE;
+ fprintf(f, "}}");
+
+ /* Check to see if we need to force a hyphen (empty first syllable) */
+ for (t = text_without_initial; t; t = t->next_character) {
+ if (t->is_character) {
+ break;
+ } else if (t->cos.s.type == ST_T_BEGIN) {
+ if (t->cos.s.style == ST_VERBATIM ||
+ t->cos.s.style == ST_SPECIAL_CHAR) {
+ break;
+ } else if (t->cos.s.style == ST_INITIAL) {
+ for (; t; t = t->next_character) {
+ if (!t->is_character && t->cos.s.type == ST_T_END
+ && t->cos.s.style == ST_INITIAL) {
+ break;
+ }
+ }
+ if (!t) break;
+ }
+ }
+ }
+ if (t || end_of_word) {
+ fprintf(f, "{}");
+ } else {
+ fprintf(f, "{\\GreEmptyFirstSyllableHyphen}");
+ }
+
+ fprintf(f, "}");
+
+ gregorio_free_characters(text_with_initial);
+ gregorio_free_characters(text_without_initial);
+ }
+}
+
+static __inline void scan_syllable_for_eol(
+ const gregorio_syllable *const syllable, char *const eol_forces_custos)
+{
+ const gregorio_element *element;
+ if (syllable->elements) {
+ for (element = *(syllable->elements); element; element = element->next) {
+ if (element->type == GRE_END_OF_LINE) {
+ if (element->u.misc.unpitched.info.eol_forces_custos) {
+ *eol_forces_custos = element->u.misc.unpitched.info
+ .eol_forces_custos_on? '1' : '0';
+ }
+ }
+ }
+ }
+}
+
+/*
+ * euouae_follows will be
+ * - '\0' if no euouae follows
+ * - '0' if euouae follows with no intervening linebreak
+ * - '1' if euouae follows with an intervening linebreak
+ *
+ * eol_forces_custos will be
+ * - '\0' if no linebreak follows or doesn't force a custos
+ * - '0' if a linebreak follows and forces custos off
+ * - '1' if a linebreak follows and forces custos on
+ */
+static __inline void anticipate_event(gregorio_syllable *syllable,
+ char *const euouae_follows, char *const eol_forces_custos,
+ unsigned short *const next_euouae_id)
+{
+ bool has_intervening_linebreak = false;
+
+ *euouae_follows = '\0';
+ *eol_forces_custos = '\0';
+ *next_euouae_id = 0;
+
+ if (syllable->next_syllable) {
+ for (syllable = syllable->next_syllable;
+ syllable && syllable->elements && *(syllable->elements)
+ && (*(syllable->elements))->type == GRE_END_OF_LINE;
+ syllable = syllable->next_syllable) {
+ has_intervening_linebreak = true;
+ /* we are at an end-of-line, so check if custos is forced */
+ scan_syllable_for_eol(syllable, eol_forces_custos);
+ }
+ if (syllable) {
+ scan_syllable_for_eol(syllable, eol_forces_custos);
+
+ if (syllable->euouae == EUOUAE_BEGINNING) {
+ *next_euouae_id = syllable->euouae_id = ++tex_position_id;
+ *euouae_follows = has_intervening_linebreak? '1' : '0';
+ }
+ }
+ }
+}
+
+static __inline void write_anticipated_event(FILE *f, const char euouae_follows,
+ const char eol_forces_custos, const short next_euouae_id)
+{
+ if (euouae_follows) {
+ fprintf(f, "%%\n\\GreNextSyllableBeginsEUOUAE{%hu}{%c}%%\n",
+ next_euouae_id, euouae_follows);
+ }
+ if (eol_forces_custos) {
+ fprintf(f, "%%\n\\GreUpcomingNewLineForcesCustos{%c}%%\n",
+ eol_forces_custos);
+ }
+}
+
+static __inline unsigned int count_note_units(const gregorio_element *element)
+{
+ unsigned int count = 0;
+ for (; element; element = element->next) {
+ if (element->type == GRE_ELEMENT) {
+ const gregorio_glyph *glyph;
+ for (glyph = element->u.first_glyph; glyph; glyph = glyph->next) {
+ if (glyph->type == GRE_GLYPH) {
+ count += glyph_note_units(glyph);
+ }
+ }
+ }
+ }
+ return count;
+}
+
+static __inline void handle_last_of_voice(FILE *const f,
+ const gregorio_syllable *syllable,
+ const gregorio_element *const element,
+ const gregorio_element *const last_of_voice)
+{
+ if (element == last_of_voice) {
+ if (syllable->next_syllable) {
+ /* check for no-element syllables that follow */
+ for (syllable = syllable->next_syllable;
+ syllable && (!syllable->elements || !*(syllable->elements));
+ syllable = syllable->next_syllable) {
+ /* just loop */
+ }
+ /* if syllable is NULL here, then all syllables that follow
+ * have no elements */
+ }
+ /* emit GreLastOfScore if we are either on the last syllable (and
+ * thus the loop above is not executed, leaving syllable at the
+ * current syllable) or if a syllable which follows the current
+ * syllable has an element of some sort (and thus the loop above
+ * stopped before running out of syllables); in any case, the check
+ * is that syllable, at this point, is not NULL */
+ if (syllable) {
+ fprintf(f, "\\GreLastOfScore");
+ }
+ }
+}
+
+static bool is_before_linebreak(const gregorio_syllable *syllable,
+ const gregorio_element *element)
+{
+ if (element) {
+ element = element->next;
+ }
+
+ while (syllable) {
+ if (element) {
+ if (element->type == GRE_END_OF_LINE) {
+ return true;
+ }
+ break;
+ }
+
+ syllable = syllable->next_syllable;
+ if (syllable) {
+ element = syllable->elements[0];
+ }
+ }
+
+ return false;
+}
+
+/*
+ * Arguments are relatively obvious. The most obscure is certainly first_of_disc
+ * which is 0 all the time, except in the case of a "clef change syllable". In
+ * this case we make a \GreDiscretionary with two arguments:
+ * 1.what should be printed if the syllable is the last of its line (which
+ * basically means everything but clefs and custos), and
+ * 2. what should be printed if it's in a middle of a line (which means
+ * everything)
+ * So the first_of_disc argument is:
+ * 0 if we don't know (general case)
+ * 1 in case of the first argument of a \GreDiscretionary
+ * 2 if we are in the second argument (necessary in order to avoid infinite loops)
+ */
+static void write_syllable(FILE *f, gregorio_syllable *syllable,
+ unsigned char first_of_disc, gregoriotex_status *const status,
+ const gregorio_score *const score,
+ const gregorio_element *const *const last_of_voice,
+ void (*const write_this_syllable_text)
+ (FILE *, const char *, const gregorio_syllable *, bool))
+{
+ const gregorio_element *clef_change_element = NULL, *element;
+ const char *syllable_type = NULL;
+ bool anticipated_event_written = false;
+ bool end_of_word;
+ bool end_of_line;
+ char euouae_follows;
+ char eol_forces_custos;
+ unsigned short next_euouae_id;
+ unsigned int note_unit_count;
+
+ gregorio_not_null(syllable, write_syllable, return);
+ end_of_word = syllable->position == WORD_END
+ || syllable->position == WORD_ONE_SYLLABLE || !syllable->text
+ || !syllable->next_syllable;
+ /* Very first: before anything, if the syllable is the beginning of a
+ * no-linebreak area: */
+ if (syllable->no_linebreak_area == NLBA_BEGINNING) {
+ fprintf(f, "\\GreBeginNLBArea{1}{0}%%\n");
+ }
+ if (syllable->euouae == EUOUAE_BEGINNING) {
+ fprintf(f, "\\GreBeginEUOUAE{%hu}%%\n", syllable->euouae_id);
+ }
+ if (syllable->elements && *(syllable->elements)) {
+ /*
+ * first we check if the syllable is only a end of line. If it is the
+ * case, we don't print anything but a comment (to be able to read it
+ * if we read GregorioTeX). The end of lines are treated separately in
+ * GregorioTeX, it is buit inside the TeX structure.
+ */
+ if ((syllable->elements)[0]->type == GRE_END_OF_LINE) {
+ gregorio_assert(syllable != score->first_syllable, write_syllable,
+ "line break is not supported on the first syllable",
+ return);
+ if ((syllable->elements)[0]->u.misc.unpitched.info.eol_ragged) {
+ fprintf(f, "%%\n%%\n\\GreNewParLine %%\n%%\n%%\n");
+ } else {
+ fprintf(f, "%%\n%%\n\\GreNewLine %%\n%%\n%%\n");
+ }
+ write_this_syllable_text(f, NULL, syllable, end_of_word);
+ return;
+ }
+ /*
+ * This case is not simple: if the syllable contains a clef change,
+ * whether it is (c4) or (::c4) or (z0::c4), we put it in a
+ * discretionary. Warning: only these three cases will have the expected
+ * effect. So first we detect it:
+ */
+ if (first_of_disc == 0) { /* to avoid infinite loops */
+ clef_change_element = gregorio_get_clef_change(syllable);
+ if (clef_change_element) {
+ gregorio_assert(syllable != score->first_syllable,
+ write_syllable,
+ "clef change is not supported on the first syllable",
+ return);
+ /*
+ * In this case, the first thing to do is to change the line clef
+ */
+ gregoriotex_print_change_line_clef(f, clef_change_element);
+ fprintf(f, "\\GreDiscretionary{0}{%%\n");
+ write_syllable(f, syllable, 1, status, score, last_of_voice,
+ write_syllable_text);
+ fprintf(f, "}{%%\n");
+ write_syllable(f, syllable, 2, status, score, last_of_voice,
+ write_syllable_text);
+ fprintf(f, "}%%\n");
+ write_this_syllable_text(f, NULL, syllable, end_of_word);
+ return;
+ }
+ }
+ write_fixed_text_styles(f, syllable->text,
+ syllable->next_syllable? syllable->next_syllable->text : NULL);
+ if ((syllable->elements)[0]->type == GRE_BAR) {
+ if (syllable != score->first_syllable) {
+ if (!syllable->next_syllable && !syllable->text
+ && (syllable->elements)[0]->u.misc.unpitched.info.bar
+ == B_DIVISIO_FINALIS) {
+ handle_final_bar(f, "DivisioFinalis", syllable);
+ write_this_syllable_text(f, NULL, syllable, end_of_word);
+ return;
+ }
+ if (!syllable->next_syllable && !syllable->text
+ && (syllable->elements)[0]->u.misc.unpitched.info.bar
+ == B_DIVISIO_MAIOR) {
+ handle_final_bar(f, "DivisioMaior", syllable);
+ write_this_syllable_text(f, NULL, syllable, end_of_word);
+ return;
+ }
+ }
+ /* the special case of first syllable will be treated as a normal
+ * bar syllable */
+ syllable_type = "\\GreBarSyllable";
+ } else {
+ if (first_of_disc != 0) {
+ syllable_type = "\\GreBarSyllable";
+ } else {
+ syllable_type = "\\GreSyllable";
+ }
+ }
+ } else {
+ write_fixed_text_styles(f, syllable->text,
+ syllable->next_syllable? syllable->next_syllable->text : NULL);
+ syllable_type = "\\GreNoNoteSyllable";
+ }
+ write_this_syllable_text(f, syllable_type, syllable, end_of_word);
+ fprintf(f, "{}{\\Gre%s}", syllable->first_word ? "FirstWord" : "Unstyled");
+ if (end_of_word) {
+ fprintf(f, "{1}");
+ } else {
+ fprintf(f, "{0}");
+ }
+ end_of_line = is_last_of_line(syllable);
+ anticipate_event(syllable, &euouae_follows, &eol_forces_custos,
+ &next_euouae_id);
+ if (syllable->next_syllable) {
+ fprintf(f, "{%s\\GreSetNextSyllable",
+ syllable->next_syllable->forced_center?
+ "\\GreGABCNextForceCenters" : "");
+ write_text(f, syllable->next_syllable->text);
+ if (end_of_line || first_of_disc == 1) {
+ fprintf(f, "\\GreLastOfLine");
+ } else if (euouae_follows) {
+ fprintf(f, "\\GreLastSyllableBeforeEUOUAE{%hu}{%c}",
+ next_euouae_id, euouae_follows);
+ }
+ fprintf(f, "}{");
+ write_syllable_point_and_click(f, syllable, status);
+ fprintf(f, "}{%d}{",
+ gregoriotex_syllable_first_type(syllable->next_syllable));
+ } else {
+ fprintf(f, "{\\GreSetNextSyllable{}{}{}{}{}");
+ if (end_of_line || first_of_disc == 1) {
+ fprintf(f, "\\GreLastOfLine");
+ }
+ fprintf(f, "}{");
+ write_syllable_point_and_click(f, syllable, status);
+ fprintf(f, "}{16}{");
+ }
+ if (syllable->translation) {
+ if (syllable->translation_type == TR_WITH_CENTER_BEGINNING) {
+ fprintf(f, "%%\n\\GreWriteTranslationWithCenterBeginning{");
+ } else {
+ fprintf(f, "%%\n\\GreWriteTranslation{");
+ }
+ write_translation(f, syllable->translation);
+ fprintf(f, "}%%\n");
+ }
+ if (syllable->translation_type) {
+ if (syllable->translation_type == TR_WITH_CENTER_END)
+ fprintf(f, "%%\n\\GreTranslationCenterEnd %%\n");
+ }
+ if (syllable->abovelinestext) {
+ fprintf(f, "%%\n\\GreSetTextAboveLines{%s}%%\n",
+ syllable->abovelinestext);
+ }
+ fprintf(f, "}{%%\n");
+
+ fprintf(f, "\\GreSyllableNoteCount{%u}%%\n", syllable->elements?
+ count_note_units(*syllable->elements) : 0);
+
+ note_unit_count = 0;
+ if (syllable->elements) {
+ for (element = *syllable->elements; element; element = element->next) {
+ if (element->nabc_lines && element->nabc) {
+ signed char high_pitch = UNDETERMINED_HEIGHT;
+ signed char low_pitch = UNDETERMINED_HEIGHT;
+ size_t i;
+ compute_element_height_extrema(element, &high_pitch,
+ &low_pitch);
+ fixup_height_extrema(&high_pitch, &low_pitch);
+ for (i = 0; i < element->nabc_lines; i++) {
+ if (element->nabc[i]) {
+ fprintf(f, "\\GreNABCNeumes{%d}{", (int)(i+1));
+ tex_escape_text(f, element->nabc[i]);
+ fprintf(f, "}{%d}{%d}%%\n", pitch_value(high_pitch),
+ pitch_value(low_pitch));
+ }
+ }
+ }
+ switch (element->type) {
+ case GRE_SPACE:
+ switch (element->u.misc.unpitched.info.space) {
+ case SP_LARGER_SPACE:
+ fprintf(f, "\\GreEndOfElement{1}{0}{%u}%%\n",
+ note_unit_count);
+ break;
+ case SP_GLYPH_SPACE:
+ fprintf(f, "\\GreEndOfElement{2}{0}{%u}%%\n",
+ note_unit_count);
+ break;
+ case SP_NEUMATIC_CUT:
+ fprintf(f, "\\GreEndOfElement{0}{0}{%u}%%\n",
+ note_unit_count);
+ break;
+ case SP_AD_HOC_SPACE:
+ fprintf(f, "\\GreAdHocSpaceEndOfElement{%s}{0}{%u}%%\n",
+ element->u.misc.unpitched.info.ad_hoc_space_factor,
+ note_unit_count);
+ break;
+ case SP_GLYPH_SPACE_NB:
+ fprintf(f, "\\GreEndOfElement{2}{1}{%u}%%\n",
+ note_unit_count);
+ break;
+ case SP_LARGER_SPACE_NB:
+ fprintf(f, "\\GreEndOfElement{1}{1}{%u}%%\n",
+ note_unit_count);
+ break;
+ case SP_NEUMATIC_CUT_NB:
+ fprintf(f, "\\GreEndOfElement{0}{1}{%u}%%\n",
+ note_unit_count);
+ break;
+ case SP_AD_HOC_SPACE_NB:
+ fprintf(f, "\\GreAdHocSpaceEndOfElement{%s}{1}{%u}%%\n",
+ element->u.misc.unpitched.info.ad_hoc_space_factor,
+ note_unit_count);
+ break;
+ default:
+ /* not reachable unless there's a programming error */
+ /* LCOV_EXCL_START */
+ gregorio_fail(write_syllable,
+ "encountered an unexpected element-level space");
+ break;
+ /* LCOV_EXCL_STOP */
+ }
+ break;
+
+ case GRE_TEXVERB_ELEMENT:
+ if (element->texverb) {
+ fprintf(f, "%% verbatim text at element level:\n%s%%\n"
+ "%% end of verbatim text\n",
+ gregorio_texverb(element->texverb));
+ }
+ break;
+
+ case GRE_NLBA:
+ if (element->u.misc.unpitched.info.nlba == NLBA_BEGINNING) {
+ fprintf(f, "\\GreBeginNLBArea{0}{0}%%\n");
+ } else {
+ fprintf(f, "\\GreEndNLBArea{%d}{0}%%\n",
+ next_is_bar(syllable, element)? 3 : 0);
+ }
+ break;
+
+ case GRE_ALT:
+ if (element->texverb) {
+ fprintf(f, "\\GreSetTextAboveLines{%s}%%\n",
+ gregorio_texverb(element->texverb));
+ }
+ break;
+
+ case GRE_CLEF:
+ /* We don't print clef changes at the end of a line */
+ if (first_of_disc != 1) {
+ if (is_before_linebreak(syllable, element)) {
+ signed char next_note_pitch;
+ gregorio_shape next_note_alteration;
+
+ next_note_pitch = gregorio_adjust_pitch_into_staff(
+ score, gregorio_determine_next_pitch(
+ syllable, element, NULL, &next_note_alteration)
+ - element->u.misc.clef.pitch_difference);
+
+ fputs(next_custos(next_note_pitch, next_note_alteration,
+ status), f);
+ gregoriotex_print_change_line_clef(f, element);
+ } else {
+ /* the third argument is 0 or 1 according to the need
+ * for a space before the clef */
+ fprintf(f, "\\GreChangeClef{%c}{%d}{%c}{%d}{%c}{%d}{%d}%%\n",
+ gregorio_clef_to_char(element->u.misc.clef.clef),
+ element->u.misc.clef.line,
+ (!element->previous || element->previous->type
+ == GRE_BAR)? '0' : '1',
+ clef_flat_height(element->u.misc.clef.clef,
+ element->u.misc.clef.line,
+ element->u.misc.clef.flatted),
+ gregorio_clef_to_char(
+ element->u.misc.clef.secondary_clef),
+ element->u.misc.clef.secondary_line,
+ clef_flat_height(
+ element->u.misc.clef.secondary_clef,
+ element->u.misc.clef.secondary_line,
+ element->u.misc.clef.secondary_flatted));
+ }
+ }
+ break;
+
+ case GRE_CUSTOS:
+ if (first_of_disc != 1) {
+ signed char next_note_pitch;
+ gregorio_shape next_note_alteration;
+ const char *alteration = "";
+ /* We don't print custos before a bar at the end of a line.
+ * We also print an unbreakable larger space before the
+ * custos */
+ handle_last_of_voice(f, syllable, element, *last_of_voice);
+ next_note_pitch = gregorio_determine_next_pitch(syllable,
+ element, NULL, &next_note_alteration);
+ if (!element->u.misc.pitched.force_pitch) {
+ alteration = alteration_name(next_note_alteration);
+ }
+ fprintf(f, "\\GreCustos{%d}{%s}%s%%\n",
+ pitch_value(element->u.misc.pitched.pitch), alteration,
+ next_custos(next_note_pitch, next_note_alteration,
+ status));
+ ++note_unit_count;
+ }
+ break;
+
+ case GRE_SUPPRESS_CUSTOS:
+ handle_last_of_voice(f, syllable, element, *last_of_voice);
+ fprintf(f, "\\GreSuppressEolCustos %%\n");
+ status->suppressed_custos = true;
+ break;
+
+ case GRE_BAR:
+ handle_last_of_voice(f, syllable, element, *last_of_voice);
+ write_bar(f, score, syllable, element, first_of_disc);
+ break;
+
+ case GRE_END_OF_LINE:
+ if (!element->next) {
+ write_anticipated_event(f, euouae_follows,
+ eol_forces_custos, next_euouae_id);
+ anticipated_event_written = true;
+ }
+ /* here we suppose we don't have two linebreaks in the same
+ * syllable */
+ if (element->u.misc.unpitched.info.eol_ragged) {
+ fprintf(f, "%%\n%%\n\\GreNewParLine %%\n%%\n%%\n");
+ } else {
+ fprintf(f, "%%\n%%\n\\GreNewLine %%\n%%\n%%\n");
+ }
+ break;
+
+ default:
+ /* here current_element->type is GRE_ELEMENT */
+ assert(element->type == GRE_ELEMENT);
+ handle_last_of_voice(f, syllable, element, *last_of_voice);
+ note_unit_count += write_element(f, syllable, element, status,
+ score);
+ if (element->next && (element->next->type == GRE_ELEMENT
+ || (element->next->next
+ && element->next->type == GRE_ALT
+ && element->next->next->type ==
+ GRE_ELEMENT))) {
+ fprintf(f, "\\GreEndOfElement{0}{0}{%u}%%\n",
+ note_unit_count);
+ }
+ break;
+ }
+ }
+ }
+ if (!anticipated_event_written) {
+ write_anticipated_event(f, euouae_follows, eol_forces_custos,
+ next_euouae_id);
+ }
+ fprintf(f, "}%%\n");
+ if (syllable->position == WORD_END
+ || syllable->position == WORD_ONE_SYLLABLE || !syllable->text) {
+ fprintf(f, "%%\n");
+ }
+ finish_syllable(f, syllable);
+}
+
+static char *digest_to_hex(const unsigned char digest[SHA1_DIGEST_SIZE])
+{
+ static const char *const hex = "0123456789abcdef";
+ static char result[41];
+
+ char *p = result;
+ unsigned char byte;
+
+ int i;
+ for (i = 0; i < SHA1_DIGEST_SIZE; ++i) {
+ byte = digest[i];
+
+ *(p++) = hex[(byte >> 4) & 0x0FU];
+ *(p++) = hex[byte & 0x0FU];
+ }
+
+ *p = '\0';
+
+ return result;
+}
+
+static void initialize_score(gregoriotex_status *const status,
+ gregorio_score *score, const bool point_and_click,
+ const gregorio_element **const last_of_voice)
+{
+ gregorio_syllable *syllable;
+
+ status->bottom_line = false;
+ status->top_height = status->bottom_height = UNDETERMINED_HEIGHT;
+ status->abovelinestext = status->translation = false;
+ status->suppressed_custos = false;
+
+ /* first pass to compute positioning */
+ for (syllable = score->first_syllable; syllable;
+ syllable = syllable->next_syllable) {
+ int voice;
+
+ for (voice = 0; voice < score->number_of_voices; ++voice) {
+ gregoriotex_compute_positioning(syllable->elements[voice], score);
+ }
+ }
+
+ gregoriotex_compute_cross_syllable_positioning(score);
+
+ for (syllable = score->first_syllable; syllable;
+ syllable = syllable->next_syllable) {
+ int voice;
+
+ if (syllable->translation) {
+ status->translation = true;
+ }
+
+ if (syllable->abovelinestext) {
+ status->abovelinestext = true;
+ }
+
+ /* simultaneously compute height extrema and determine the last "real"
+ * element in each voice */
+ for (voice = 0; voice < score->number_of_voices; ++voice) {
+ gregorio_element *element;
+
+ for (element = syllable->elements[voice]; element;
+ element = element->next) {
+ gregorio_glyph *glyph;
+
+ switch (element->type) {
+ case GRE_ALT:
+ status->abovelinestext = true;
+ break;
+
+ case GRE_CUSTOS:
+ last_of_voice[voice] = element;
+ break;
+
+ case GRE_ELEMENT:
+ for (glyph = element->u.first_glyph; glyph;
+ glyph = glyph->next) {
+ if (glyph->type == GRE_GLYPH) {
+ last_of_voice[voice] = element;
+ compute_height_extrema(glyph,
+ glyph->u.notes.first_note,
+ &(status->top_height),
+ &(status->bottom_height));
+ }
+ }
+ break;
+
+ case GRE_BAR:
+ last_of_voice[voice] = element;
+ break;
+
+ default:
+ /* to eliminate the warning */
+ break;
+ }
+ }
+ }
+ }
+
+ fixup_height_extrema(&(status->top_height), &(status->bottom_height));
+
+ status->point_and_click = point_and_click;
+}
+
+static void write_header(FILE *const f, const char *const name,
+ const char *const value)
+{
+ if (value) {
+ fprintf(f, "\\GreHeader{");
+ tex_escape_text(f, name);
+ fprintf(f, "}{");
+ tex_escape_text(f, value);
+ fprintf(f, "}%%\n");
+ }
+}
+
+static void write_headers(FILE *const f, gregorio_score *const score)
+{
+ gregorio_header *header;
+
+ fprintf(f, "\\GreBeginHeaders %%\n");
+ for (header = score->headers; header; header = header->next) {
+ write_header(f, header->name, header->value);
+ }
+ fprintf(f, "\\GreEndHeaders %%\n");
+}
+
+static void suppress_expansion(FILE *const f, const char *text)
+{
+ if (!text) {
+ return;
+ }
+
+ for (; *text; ++text) {
+ if (*text == '\\') {
+ fprintf(f, "\\noexpand");
+ }
+ fputc(*text, f);
+ }
+}
+
+static int first_note_near_clef(const gregorio_score *const score) {
+ gregorio_clef_info clef = gregorio_default_clef;
+ if (score->first_voice_info) {
+ clef = score->first_voice_info->initial_clef;
+ if (!clef.secondary_line && !clef.flatted && score->first_syllable
+ && score->first_syllable->elements) {
+ const gregorio_element *element = score->first_syllable->elements[0];
+ if (element && element->type == GRE_ELEMENT) {
+ const gregorio_glyph *glyph = element->u.first_glyph;
+ if (glyph && glyph->type == GRE_GLYPH) {
+ const signed char clef_pitch = LOW_LINE_PITCH
+ + ((clef.line - 1) * 2);
+ const gregorio_note *low_note = glyph->u.notes.first_note;
+ const gregorio_note *high_note = low_note;
+ switch (glyph->u.notes.glyph_type) {
+ case G_PODATUS:
+ /* next note is above the previous */
+ if (low_note->next) {
+ high_note = low_note->next;
+ }
+ break;
+ case G_FLEXA:
+ case G_PORRECTUS:
+ case G_PORRECTUS_FLEXUS:
+ /* there is a stem the size of the ambitus */
+ if (high_note->next) {
+ low_note = high_note->next;
+ }
+ break;
+ default:
+ /* to prevent the enum warning */
+ break;
+ }
+ if (high_note->u.note.pitch < clef_pitch - 3
+ || low_note->u.note.pitch > clef_pitch + 3) {
+ return 0;
+ }
+ }
+ }
+ }
+ }
+ return 1;
+}
+
+static int clef_size(const gregorio_clef_info *const clef)
+{
+ /* this is a heuristic clef size approximation:
+ * C-clef = 1
+ * F-clef = 2
+ * flatted = +2
+ * unstacked double-clef = +1 (plus the clef size)
+ */
+ /* purposely making line signed */
+ int line_diff = clef->line - clef->secondary_line;
+ int size;
+ if (clef->clef == CLEF_F) {
+ size = 2;
+ } else {
+ size = 1;
+ }
+ if (clef->flatted) {
+ size += 2;
+ }
+ if (clef->secondary_line) {
+ if (line_diff < -1 || line_diff > 1) { /* stacked */
+ int secondary_size;
+ if (clef->secondary_clef == CLEF_F) {
+ secondary_size = 2;
+ } else {
+ secondary_size = 1;
+ }
+ if (clef->secondary_flatted) {
+ secondary_size += 2;
+ }
+ if (secondary_size > size) {
+ size = secondary_size;
+ }
+ } else { /* unstacked */
+ size += 1;
+ if (clef->secondary_clef == CLEF_F) {
+ size += 2;
+ } else {
+ size += 1;
+ }
+ if (clef->secondary_flatted) {
+ size += 2;
+ }
+ }
+ }
+ return size;
+}
+
+static void write_largest_clef(FILE *const f, gregorio_score *const score)
+{
+ const gregorio_syllable *syllable;
+ const gregorio_element *element;
+ gregorio_clef_info clef = gregorio_default_clef;
+ int size;
+
+ if (score->first_voice_info) {
+ clef = score->first_voice_info->initial_clef;
+ }
+ size = clef_size(&clef);
+
+ for (syllable = score->first_syllable; syllable;
+ syllable = syllable->next_syllable) {
+ if (syllable->elements) {
+ for (element = *syllable->elements; element;
+ element = element->next) {
+ if (element->type == GRE_CLEF) {
+ const int this_size = clef_size(&element->u.misc.clef);
+ if (this_size > size) {
+ clef = element->u.misc.clef;
+ size = this_size;
+ }
+ }
+ }
+ }
+ }
+
+ fprintf(f, "\\GreSetLargestClef{%c}{%d}{%d}{%c}{%d}{%d}%%\n",
+ gregorio_clef_to_char(clef.clef), clef.line,
+ clef_flat_height(clef.clef, clef.line, clef.flatted),
+ gregorio_clef_to_char(clef.secondary_clef), clef.secondary_line,
+ clef_flat_height(clef.secondary_clef, clef.secondary_line,
+ clef.secondary_flatted));
+}
+
+void gregoriotex_write_score(FILE *const f, gregorio_score *const score,
+ const char *const point_and_click_filename)
+{
+ gregorio_clef_info clef = gregorio_default_clef;
+ gregorio_syllable *current_syllable;
+ int annotation_num;
+ gregoriotex_status status;
+ const gregorio_element *last_of_voice[MAX_NUMBER_OF_VOICES];
+
+ memset(last_of_voice, 0, sizeof last_of_voice);
+ initialize_score(&status, score, point_and_click_filename != NULL,
+ last_of_voice);
+
+ gregorio_assert(f, gregoriotex_write_score, "call with NULL file", return);
+
+ gregorio_assert_only(score->number_of_voices == 1, gregoriotex_write_score,
+ "gregoriotex only works in monophony (for the moment)");
+
+ fprintf(f, "%% File generated by gregorio %s\n", GREGORIO_VERSION);
+ fprintf(f, "\\GregorioTeXAPIVersion{%s}%%\n", VERSION);
+
+ if (score->name) {
+ fprintf(f, "%% Name: %s\n", score->name);
+ }
+ if (score->author) {
+ fprintf(f, "%% Author: %s\n", score->author);
+ }
+ if (score->gabc_copyright) {
+ fprintf(f, "%% The copyright of this gabc is: %s\n",
+ score->gabc_copyright);
+ }
+ if (score->score_copyright) {
+ fprintf(f, "%% The copyright of the score is: %s\n",
+ score->score_copyright);
+ }
+
+ write_headers(f, score);
+
+ if (score->first_voice_info) {
+ clef = score->first_voice_info->initial_clef;
+ }
+ fprintf(f, "\\GreBeginScore{%s}{%d}{%d}{%d}{%d}{%s}{%u}"
+ "{\\GreInitialClefPosition{%d}{%d}}%%\n",
+ digest_to_hex(score->digest), status.top_height,
+ status.bottom_height, bool_to_int(status.translation),
+ bool_to_int(status.abovelinestext),
+ point_and_click_filename? point_and_click_filename : "",
+ score->staff_lines, clef.line, clef.secondary_line);
+ if (score->nabc_lines) {
+ fprintf(f, "\\GreScoreNABCLines{%d}", (int)score->nabc_lines);
+ }
+ if (score->annotation[0]) {
+ fprintf(f, "\\GreAnnotationLines");
+ for (annotation_num = 0; annotation_num < MAX_ANNOTATIONS;
+ ++annotation_num) {
+ if (score->annotation[annotation_num]) {
+ fprintf(f, "{%s}",
+ score->annotation[annotation_num]);
+ }
+ else {
+ fprintf(f, "{}");
+ }
+ }
+ fprintf(f, "%%\n");
+ }
+ if (score->mode) {
+ fprintf(f, "\\GreMode{");
+ if (*(score->mode) >= '1' && *(score->mode) <= '8') {
+ fprintf(f, "\\GreModeNumber{%c}%s", *(score->mode), score->mode + 1);
+ } else {
+ fprintf(f, "%s", score->mode);
+ }
+ fprintf(f, "}{");
+ suppress_expansion(f, score->mode_modifier);
+ fprintf(f, "}{");
+ suppress_expansion(f, score->mode_differentia);
+ fprintf(f, "}%%\n");
+ }
+
+ write_largest_clef(f, score);
+ fprintf(f, "\\GreScoreOpening{%%\n"); /* GreScoreOpening#1 */
+ if (score->first_voice_info) {
+ gregoriotex_write_voice_info(f, score->first_voice_info);
+ }
+ fprintf(f, "}{%%\n"); /* GreScoreOpening#2 */
+ fprintf(f, "\\GreSetInitialClef{%c}{%d}{%d}{%c}{%d}{%d}{%d}%%\n",
+ gregorio_clef_to_char(clef.clef), clef.line,
+ clef_flat_height(clef.clef, clef.line, clef.flatted),
+ gregorio_clef_to_char(clef.secondary_clef), clef.secondary_line,
+ clef_flat_height(clef.secondary_clef, clef.secondary_line,
+ clef.secondary_flatted),
+ first_note_near_clef(score));
+ fprintf(f, "}{%%\n"); /* GreScoreOpening#3 */
+ current_syllable = score->first_syllable;
+ if (current_syllable) {
+ write_syllable(f, current_syllable, 0, &status, score, last_of_voice,
+ write_first_syllable_text);
+ current_syllable = current_syllable->next_syllable;
+ } else {
+ /* edge case: a score with no syllables */
+ fprintf(f, "}{}{\\GreSetNoFirstSyllableText}%%\n");
+ }
+ while (current_syllable) {
+ write_syllable(f, current_syllable, 0, &status, score, last_of_voice,
+ write_syllable_text);
+ current_syllable = current_syllable->next_syllable;
+ }
+ fprintf(f, "\\GreEndScore %%\n\\endinput %%\n");
+}
diff --git a/Build/source/texk/gregorio/gregorio-src/src/gregoriotex/gregoriotex.h b/Build/source/texk/gregorio/gregorio-src/src/gregoriotex/gregoriotex.h
new file mode 100644
index 00000000000..dc662b25ffe
--- /dev/null
+++ b/Build/source/texk/gregorio/gregorio-src/src/gregoriotex/gregoriotex.h
@@ -0,0 +1,151 @@
+/*
+ * Gregorio is a program that translates gabc files to GregorioTeX
+ * This header prototypes GregorioTeX writing data structures and entry points.
+ *
+ * Copyright (C) 2006-2019 The Gregorio Project (see CONTRIBUTORS.md)
+ *
+ * This file is part of Gregorio.
+ *
+ * This program 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.
+ *
+ * This program 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
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GREGORIOTEX_H
+#define GREGORIOTEX_H
+
+#include "bool.h"
+
+#define OFFSET_CASE(name) static const char *const name = #name
+
+/*
+ * Here are the different types, they must be the same as in squarize.py
+ */
+
+typedef enum gtex_type {
+ T_ONE_NOTE = 1,
+ /* two note neumes */
+ T_PES,
+ T_PES_QUADRATUM,
+ T_QUILISMA_PES,
+ T_PES_QUASSUS,
+ T_QUILISMA_PES_QUADRATUM,
+ T_FLEXUS,
+ T_FLEXUS_ORISCUS,
+ T_FLEXUS_ORISCUS_SCAPUS,
+ T_PES_ORISCUS,
+ /* three note neumes */
+ T_PORRECTUS,
+ T_TORCULUS,
+ T_TORCULUS_QUILISMA,
+ T_SCANDICUS, /* only deminutus */
+ T_ANCUS, /* only deminutus */
+ T_SALICUS,
+ /* four note neumes */
+ T_SALICUS_FLEXUS,
+ T_PORRECTUS_FLEXUS,
+ T_TORCULUS_RESUPINUS,
+ T_TORCULUS_LIQUESCENS,
+ T_TORCULUS_RESUPINUS_FLEXUS
+} gtex_type;
+
+/* the different types for the alignment of the notes in GregorioTeX
+ * these values are numbers coded into GregorioTeX */
+typedef enum gtex_alignment {
+ AT_ONE_NOTE = 0,
+ AT_FLEXUS = 1,
+ AT_PORRECTUS = 2,
+ AT_INITIO_DEBILIS = 3,
+ AT_QUILISMA = 4,
+ AT_ORISCUS = 5,
+ AT_PUNCTUM_INCLINATUM = 6,
+ AT_STROPHA = 7,
+ AT_FLEXUS_1 = 8,
+ AT_FLEXUS_DEMINUTUS = 9
+} gtex_alignment;
+
+/* Here we define a function that will determine the number of the
+ * liquescentia that we will add to the glyph number. There are several types
+ * as all glyphs can't have all liquescentiae. Let's first define the
+ * different types: */
+
+typedef enum gtex_glyph_liquescentia {
+ LG_ALL = 0, /* for glyphs that accept all liquecentiae */
+ LG_NO_INITIO, /* for glyphs that don't accept initio debilis */
+ LG_NONE, /* for glyphs that don't accept liquescentia */
+ LG_ONLY_DEMINUTUS,
+ LG_FUSIBLE_INITIO
+} gtex_glyph_liquescentia;
+
+typedef enum gtex_sign_type {
+ ST_H_EPISEMA = 0,
+ ST_V_EPISEMA = 1
+} gtex_sign_type;
+
+#define HEPISEMA_FIRST_TWO 12
+
+static __inline bool choral_sign_here_is_low(const gregorio_glyph *const glyph,
+ const gregorio_note *const note, bool *const kind_of_pes)
+{
+ if (kind_of_pes) {
+ *kind_of_pes = false;
+ }
+
+ switch (glyph->u.notes.glyph_type) {
+ case G_FLEXA:
+ case G_TORCULUS:
+ case G_TORCULUS_LIQUESCENS:
+ case G_TORCULUS_RESUPINUS_FLEXUS:
+ case G_PORRECTUS_FLEXUS:
+ if (!note->next) {
+ return true;
+ }
+ break;
+
+ case G_PODATUS:
+ case G_PORRECTUS:
+ case G_TORCULUS_RESUPINUS:
+ if (!note->next || note->next->next) {
+ break;
+ }
+ if (kind_of_pes) {
+ *kind_of_pes = true;
+ }
+ return true;
+
+ default:
+ break;
+ }
+
+ return false;
+}
+
+static __inline bool is_on_a_line(const char pitch)
+{
+ return pitch % 2 == 0;
+}
+
+static __inline bool is_between_lines(const char pitch)
+{
+ return pitch % 2 == 1;
+}
+
+bool gtex_is_h_episema_above_shown(const gregorio_note *const note);
+bool gtex_is_h_episema_below_shown(const gregorio_note *const note);
+const char *gregoriotex_determine_glyph_name(const gregorio_glyph *const glyph,
+ gtex_alignment *const type, gtex_type *const gtype);
+void gregoriotex_compute_positioning(const gregorio_element *element,
+ const gregorio_score *score);
+void gregoriotex_compute_cross_syllable_positioning(
+ const gregorio_score *score);
+
+#endif