positionNotationElement method

bool positionNotationElement(
  1. ChantContext ctxt,
  2. List<Lyric> prevLyrics,
  3. ChantNotationElement prev,
  4. ChantNotationElement curr,
  5. double rightNotationBoundary, [
  6. List<CondensableSpace>? condensableSpaces,
])

Implementation

bool positionNotationElement(
  ChantContext ctxt,
  List<Lyric> prevLyrics,
  ChantNotationElement prev,
  ChantNotationElement curr,
  double rightNotationBoundary, [
  List<CondensableSpace>? condensableSpaces,
]) {
  final spaces = condensableSpaces ?? [];
  if (spaces.sum == 0 && spaces.isEmpty) {
    spaces.sum = 0;
  }

  int i;
  final space = CondensableSpace(notation: curr);
  bool fixedX = false;

  if ((curr.hasNoWidth == false || curr.firstWithNoWidth == curr) &&
      prev.firstWithNoWidth != null) {
    curr.bounds = curr.bounds.copyWith(x: prev.firstWithNoWidth!.bounds.x);
    fixedX = true;
  } else {
    curr.bounds = curr.bounds.copyWith(x: prev.bounds.right);
  }

  if ((curr is TextOnly && extraTextOnlyIndex == null) ||
      (!curr.hasLyrics && prev.calculatedTrailingSpace < 0)) {
    curr.calculatedTrailingSpace = prev.calculatedTrailingSpace;
    if (curr.hasLyrics) {
      curr.calculatedTrailingSpace -= curr.lyrics[0].bounds.width;
    }
    if (curr is TextOnly && curr.lyrics.length == 1) {
      curr.lyrics[0].setMaxWidth(
        ctxt,
        staffRight,
        staffRight -
            LyricArray.getRight(prevLyrics) -
            ctxt.minLyricWordSpacing,
      );
    }
  } else if (!fixedX) {
    curr.bounds = curr.bounds.copyWith(
      x: curr.bounds.x + prev.calculatedTrailingSpace,
    );
  }

  if (curr.hasLyrics &&
      prev is! Divider &&
      prev is! Accidental &&
      numNotationsOnLine > 0 &&
      (curr.lyrics[0].lyricType == LyricType.singleSyllable ||
          curr.lyrics[0].lyricType == LyricType.beginningSyllable)) {
    curr.bounds = curr.bounds.copyWith(
      x: curr.bounds.x + ctxt.intraNeumeSpacing * ctxt.interVerbalMultiplier,
    );
  }

  if (curr.hasNoWidth || fixedX) {
    space.total = 0;
    space.condensable = 0;
  } else if (extraTextOnlyIndex != null && curr is TextOnly) {
    curr.bounds = curr.bounds.copyWith(x: 0);
    space.total = 0;
    space.condensable = 0;
  } else {
    space.total = curr.bounds.x - prev.bounds.right;
    space.condensable = space.total * ctxt.condensingTolerance;
  }

  if (prevLyrics.isEmpty) {
    double maxRight = curr.bounds.right + curr.calculatedTrailingSpace;
    for (i = 0; i < curr.lyrics.length; i++) {
      final currLyric = curr.lyrics[i];
      bool needsConnector =
          currLyric.allowsConnector &&
          currLyric.dropCap != null &&
          currLyric.originalText.isNotEmpty &&
          currLyric.text.isEmpty;
      currLyric.setNeedsConnector(needsConnector);
      final minLeft = staffLeft - paddingLeft;
      if (currLyric.getLeft() < minLeft) {
        curr.bounds = curr.bounds.copyWith(
          x: curr.bounds.x - currLyric.getLeft() + minLeft,
        );
      }
      space.condensable = math.min(
        space.condensable,
        currLyric.getLeft() - minLeft,
      );
      maxRight = math.max(maxRight, currLyric.getRight());
    }

    if (maxRight > rightNotationBoundary + spaces.sum + space.condensable) {
      return false;
    }
    spaces.add(space);
    spaces.sum += space.condensable;
    return true;
  } else {
    if (curr.firstOfSyllable != null &&
        prevLyrics.isNotEmpty &&
        !curr.hasLyrics) {
      curr.bounds = curr.bounds.copyWith(
        x: math.max(curr.bounds.x, prevLyrics[0].getRight()),
      );
      space.total = curr.bounds.x - prev.bounds.right;
      space.condensable = space.total * ctxt.condensingTolerance;
    }
  }

  if (!curr.hasLyrics) {
    if (curr.bounds.right + curr.calculatedTrailingSpace >
        rightNotationBoundary + spaces.sum + space.condensable) {
      return false;
    }
    spaces.add(space);
    spaces.sum += space.condensable;
    return true;
  }

  bool hasShifted;
  bool atLeastOneWithoutConnector;
  do {
    hasShifted = false;
    atLeastOneWithoutConnector = false;
    for (i = 0; i < curr.lyrics.length; i++) {
      if (curr.lyrics[i].originalText.isEmpty) continue;
      double prevLyricRight = 0;
      var condensableSpacesSincePrevLyric = <CondensableSpace>[];
      double condensableSpaceSincePrevLyric = 0;
      if (i < prevLyrics.length) {
        prevLyricRight = prevLyrics[i].getRight();
        final notationI = spaces
            .map((s) => s.notation)
            .toList()
            .lastIndexOf(prevLyrics[i].notation!);
        if (notationI >= 0) {
          condensableSpacesSincePrevLyric = spaces.sublist(notationI + 1);
          condensableSpaceSincePrevLyric = condensableSpacesSincePrevLyric
              .fold(0.0, (sum, s) => sum + s.condensable);
        } else {
          condensableSpaceSincePrevLyric = 0;
        }
      }

      curr.lyrics[i].setNeedsConnector(false);
      final currLyricLeft = curr.lyrics[i].getLeft();
      if (prevLyrics.length <= i || !prevLyrics[i].allowsConnector) {
        final extraSpace =
            currLyricLeft - prevLyricRight - ctxt.minLyricWordSpacing;
        if (extraSpace < 0) {
          final shift =
              prevLyricRight + ctxt.minLyricWordSpacing - currLyricLeft;
          curr.bounds = curr.bounds.copyWith(x: curr.bounds.x + shift);
          condensableSpaceSincePrevLyric = 0;
          hasShifted = shift > 0.5;
        } else {
          condensableSpaceSincePrevLyric = extraSpace;
        }
      } else {
        bool overlap =
            prevLyricRight + 2.0 >
            currLyricLeft -
                condensableSpaceSincePrevLyric -
                space.condensable;
        if (overlap) {
          final shift = prevLyricRight + 2.0 - currLyricLeft;
          if (shift < -0.1) {
            final multiplier =
                shift / (condensableSpaceSincePrevLyric + space.condensable);
            double offset = 0;
            for (final s in condensableSpacesSincePrevLyric) {
              offset += multiplier * s.condensable;
              s.notation.bounds = s.notation.bounds.copyWith(
                x: s.notation.bounds.x + offset,
              );
            }
          }
          curr.bounds = curr.bounds.copyWith(x: curr.bounds.x + shift);
          condensableSpaceSincePrevLyric = 0;
          atLeastOneWithoutConnector = true;
          hasShifted = shift > 0.5;
        } else {
          if (ctxt.minLyricWordSpacing < ctxt.hyphenWidth) {
            final spaceBetweenSyls = currLyricLeft - prevLyricRight;
            if (spaceBetweenSyls < ctxt.hyphenWidth) {
              final minHyphenWidth = prevLyrics.length > 1
                  ? ctxt.intraNeumeSpacing
                  : ctxt.minLyricWordSpacing;
              prevLyrics[i].setConnectorWidth(
                math.max(minHyphenWidth, spaceBetweenSyls),
              );
            }
          }
          prevLyrics[i].setNeedsConnector(true);
          prevLyricRight = prevLyrics[i].getRight();
          if (prevLyricRight + 0.1 > currLyricLeft) {
            final shift = prevLyricRight - currLyricLeft;
            curr.bounds = curr.bounds.copyWith(x: curr.bounds.x + shift);
            condensableSpaceSincePrevLyric = 0;
            hasShifted = shift > 0.5;
          } else {
            condensableSpaceSincePrevLyric = currLyricLeft - prevLyricRight;
          }
        }
      }

      if (condensableSpaceSincePrevLyric != 0) {
        final previousCondensableSpaceSum = condensableSpacesSincePrevLyric
            .fold(0.0, (sum, s) => sum + s.condensable);
        if (condensableSpaceSincePrevLyric <
            previousCondensableSpaceSum + space.condensable) {
          final multiplier =
              condensableSpaceSincePrevLyric /
              (previousCondensableSpaceSum + space.condensable);
          space.condensable *= multiplier;
          if (condensableSpacesSincePrevLyric.isNotEmpty) {
            for (final s in condensableSpacesSincePrevLyric) {
              s.condensable *= multiplier;
            }
            spaces.sum = spaces
                .map((s) => s.condensable)
                .fold(0.0, (a, b) => a + b);
          }
        }
      }
    }
  } while (curr.lyrics.length > 1 &&
      hasShifted &&
      atLeastOneWithoutConnector);

  for (i = math.min(curr.lyrics.length, prevLyrics.length) - 1; i >= 0; i--) {
    final pLyrics = prevLyrics[i];
    if (pLyrics.needsConnector && pLyrics.connectorWidth != null) {
      final currLyricLeft = curr.lyrics[i].getLeft();
      final prevLyricRight = pLyrics.getRight() - pLyrics.connectorWidth!;
      double spaceBetweenSyls = currLyricLeft - prevLyricRight;
      if (spaceBetweenSyls >= ctxt.hyphenWidth) {
        spaceBetweenSyls = 0;
      }
      pLyrics.setConnectorWidth(spaceBetweenSyls);
    }
  }

  if (curr.bounds.right + curr.calculatedTrailingSpace <
          rightNotationBoundary + spaces.sum + space.condensable &&
      LyricArray.getRight(curr.lyrics, true) <=
          staffRight + spaces.sum + space.condensable) {
    if (prev is Accidental) {
      final shift =
          curr.bounds.x -
          prev.bounds.width -
          prev.calculatedTrailingSpace -
          prev.bounds.x;
      prev.bounds = prev.bounds.copyWith(x: prev.bounds.x + shift);
      if (shift.abs() > 0.1) {
        final lastCondensable = spaces[spaces.length - 1];
        spaces.sum -= lastCondensable.condensable;
        lastCondensable.condensable = 0;
      }
    }
    spaces.add(space);
    spaces.sum += space.condensable;
    return true;
  }

  return false;
}