/**
   * Hide the atoms and bonds of a contracted abbreviation. If the abbreviations is attached we
   * remap the attachment symbol to display the name. If there are no attachments the symbol we be
   * added later ({@see #generateSgroups}).
   *
   * @param container molecule
   * @param sgroup abbreviation group display shortcut
   */
  private static void contractAbbreviation(
      IAtomContainer container, Map<IAtom, String> symbolRemap, Sgroup sgroup) {

    final Set<IBond> crossing = sgroup.getBonds();
    final Set<IAtom> atoms = sgroup.getAtoms();

    // only do 0,1 attachments for now
    if (crossing.size() > 1) return;

    for (IAtom atom : atoms) {
      StandardGenerator.hide(atom);
    }
    for (IBond bond : container.bonds()) {
      if (atoms.contains(bond.getAtom(0)) || atoms.contains(bond.getAtom(1)))
        StandardGenerator.hide(bond);
    }
    for (IBond bond : crossing) {
      StandardGenerator.unhide(bond);
      IAtom a1 = bond.getAtom(0);
      IAtom a2 = bond.getAtom(1);
      StandardGenerator.unhide(a1);
      if (atoms.contains(a1)) symbolRemap.put(a1, sgroup.getSubscript());
      StandardGenerator.unhide(a2);
      if (atoms.contains(a2)) symbolRemap.put(a2, sgroup.getSubscript());
    }
  }
 private IRenderingElement generateAbbreviationSgroup(Sgroup sgroup) {
   String label = sgroup.getSubscript();
   // already handled by symbol remapping
   if (sgroup.getBonds().size() > 0 || label == null || label.isEmpty()) {
     return new ElementGroup();
   }
   // we're showing a label where there were no atoms before, we put it in the
   // middle of all of those which were hidden
   final Point2d labelCoords = GeometryUtil.get2DCenter(sgroup.getAtoms());
   ElementGroup group = new ElementGroup();
   for (Shape outline :
       atomGenerator
           .generatePseudoSymbol(label, HydrogenPosition.Right)
           .resize(1 / scale, 1 / -scale)
           .getOutlines()) group.add(GeneralPath.shapeOf(outline, foreground));
   return MarkedElement.markupAtom(group, null);
 }
  /**
   * Hide the repeated atoms and bonds of a multiple group. We hide al atoms that belong to the
   * group unless they are defined in the parent atom list. Any bond to those atoms that is not a
   * crossing bond or one connecting atoms in the parent list is hidden.
   *
   * @param container molecule
   * @param sgroup multiple group display shortcut
   */
  private static void hideMultipleParts(IAtomContainer container, Sgroup sgroup) {

    final Set<IBond> crossing = sgroup.getBonds();
    final Set<IAtom> atoms = sgroup.getAtoms();
    final Set<IAtom> parentAtoms = sgroup.getValue(SgroupKey.CtabParentAtomList);

    for (IBond bond : container.bonds()) {
      if (parentAtoms.contains(bond.getAtom(0)) && parentAtoms.contains(bond.getAtom(1))) continue;
      if (atoms.contains(bond.getAtom(0)) || atoms.contains(bond.getAtom(1)))
        StandardGenerator.hide(bond);
    }
    for (IAtom atom : atoms) {
      if (!parentAtoms.contains(atom)) StandardGenerator.hide(atom);
    }
    for (IBond bond : crossing) {
      StandardGenerator.unhide(bond);
    }
  }
  private IRenderingElement generateSgroupBrackets(
      Sgroup sgroup,
      List<SgroupBracket> brackets,
      String subscriptSuffix,
      String superscriptSuffix) {

    // brackets are square by default (style:0)
    Integer style = sgroup.getValue(SgroupKey.CtabBracketStyle);
    boolean round = style != null && style == 1;
    ElementGroup result = new ElementGroup();

    Set<IAtom> atoms = sgroup.getAtoms();
    Set<IBond> crossingBonds = sgroup.getBonds();

    // easy to depict in correct orientation, we just
    // point each bracket at the atom of a crossing
    // bond that is 'in' the group - this scales
    // to more than two brackets

    // first we need to pair the brackets with the bonds
    Map<SgroupBracket, IBond> pairs =
        crossingBonds.size() == brackets.size()
            ? bracketBondPairs(brackets, crossingBonds)
            : Collections.<SgroupBracket, IBond>emptyMap();

    if (!pairs.isEmpty()) {

      SgroupBracket suffixBracket = null;
      Vector2d suffixBracketPerp = null;

      for (Map.Entry<SgroupBracket, IBond> e : pairs.entrySet()) {

        final SgroupBracket bracket = e.getKey();
        final IBond bond = e.getValue();
        final IAtom inGroupAtom =
            atoms.contains(bond.getAtom(0)) ? bond.getAtom(0) : bond.getAtom(1);

        final Point2d p1 = bracket.getFirstPoint();
        final Point2d p2 = bracket.getSecondPoint();

        final Vector2d perp = VecmathUtil.newPerpendicularVector(VecmathUtil.newUnitVector(p1, p2));

        // point the vector at the atom group
        Point2d midpoint = VecmathUtil.midpoint(p1, p2);
        if (perp.dot(VecmathUtil.newUnitVector(midpoint, inGroupAtom.getPoint2d())) < 0) {
          perp.negate();
        }

        perp.scale(bracketDepth);

        Path2D path = new Path2D.Double();
        if (round) {
          // bracket 1 (cp: control point)
          path.moveTo(p1.x + perp.x, p1.y + perp.y);
          Point2d cpb1 = new Point2d(midpoint);
          cpb1.add(VecmathUtil.negate(perp));
          path.quadTo(cpb1.x, cpb1.y, p2.x + perp.x, p2.y + p2.y);
        } else {
          path.moveTo(p1.x + perp.x, p1.y + perp.y);
          path.lineTo(p1.x, p1.y);
          path.lineTo(p2.x, p2.y);
          path.lineTo(p2.x + perp.x, p2.y + perp.y);
        }
        result.add(GeneralPath.outlineOf(path, stroke, foreground));

        if (suffixBracket == null) {
          suffixBracket = bracket;
          suffixBracketPerp = perp;
        } else {
          // is this bracket better as a suffix?
          Point2d sp1 = suffixBracket.getFirstPoint();
          Point2d sp2 = suffixBracket.getSecondPoint();
          double bestMaxX = Math.max(sp1.x, sp2.x);
          double thisMaxX = Math.max(p1.x, p2.x);
          double bestMaxY = Math.max(sp1.y, sp2.y);
          double thisMaxY = Math.max(p1.y, p2.y);

          // choose the most eastern or.. the most southern
          double xDiff = thisMaxX - bestMaxX;
          double yDiff = thisMaxY - bestMaxY;
          if (xDiff > EQUIV_THRESHOLD || (xDiff > -EQUIV_THRESHOLD && yDiff < -EQUIV_THRESHOLD)) {
            suffixBracket = bracket;
            suffixBracketPerp = perp;
          }
        }
      }

      // write the labels
      if (suffixBracket != null) {

        Point2d subSufPnt = suffixBracket.getFirstPoint();
        Point2d supSufPnt = suffixBracket.getSecondPoint();

        // try to put the subscript on the bottom
        double xDiff = subSufPnt.x - supSufPnt.x;
        double yDiff = subSufPnt.y - supSufPnt.y;
        if (yDiff > EQUIV_THRESHOLD || (yDiff > -EQUIV_THRESHOLD && xDiff > EQUIV_THRESHOLD)) {
          Point2d tmpP = subSufPnt;
          subSufPnt = supSufPnt;
          supSufPnt = tmpP;
        }

        // subscript/superscript suffix annotation
        if (subscriptSuffix != null && !subscriptSuffix.isEmpty()) {
          TextOutline subscriptOutline =
              leftAlign(
                  makeText(
                      subscriptSuffix.toLowerCase(Locale.ROOT),
                      subSufPnt,
                      suffixBracketPerp,
                      labelScale));
          result.add(GeneralPath.shapeOf(subscriptOutline.getOutline(), foreground));
        }
        if (superscriptSuffix != null && !superscriptSuffix.isEmpty()) {
          TextOutline superscriptOutline =
              leftAlign(
                  makeText(
                      superscriptSuffix.toLowerCase(Locale.ROOT),
                      supSufPnt,
                      suffixBracketPerp,
                      labelScale));
          result.add(GeneralPath.shapeOf(superscriptOutline.getOutline(), foreground));
        }
      }
    } else if (brackets.size() == 2) {

      final Point2d b1p1 = brackets.get(0).getFirstPoint();
      final Point2d b1p2 = brackets.get(0).getSecondPoint();
      final Point2d b2p1 = brackets.get(1).getFirstPoint();
      final Point2d b2p2 = brackets.get(1).getSecondPoint();

      final Vector2d b1vec = VecmathUtil.newUnitVector(b1p1, b1p2);
      final Vector2d b2vec = VecmathUtil.newUnitVector(b2p1, b2p2);

      final Vector2d b1pvec = VecmathUtil.newPerpendicularVector(b1vec);
      final Vector2d b2pvec = VecmathUtil.newPerpendicularVector(b2vec);

      // Point the vectors at each other
      if (b1pvec.dot(VecmathUtil.newUnitVector(b1p1, b2p1)) < 0) b1pvec.negate();
      if (b2pvec.dot(VecmathUtil.newUnitVector(b2p1, b1p1)) < 0) b2pvec.negate();

      // scale perpendicular vectors by how deep the brackets need to be
      b1pvec.scale(bracketDepth);
      b2pvec.scale(bracketDepth);

      // bad brackets
      if (Double.isNaN(b1pvec.x)
          || Double.isNaN(b1pvec.y)
          || Double.isNaN(b2pvec.x)
          || Double.isNaN(b2pvec.y)) return result;

      Path2D path = new Path2D.Double();

      if (round) {
        // bracket 1 (cp: control point)
        path.moveTo(b1p1.x + b1pvec.x, b1p1.y + b1pvec.y);
        Point2d cpb1 = VecmathUtil.midpoint(b1p1, b1p2);
        cpb1.add(VecmathUtil.negate(b1pvec));
        path.quadTo(cpb1.x, cpb1.y, b1p2.x + b1pvec.x, b1p2.y + b1pvec.y);

        // bracket 2 (cp: control point)
        path.moveTo(b2p1.x + b2pvec.x, b2p1.y + b2pvec.y);
        Point2d cpb2 = VecmathUtil.midpoint(b2p1, b2p2);
        cpb2.add(VecmathUtil.negate(b2pvec));
        path.quadTo(cpb2.x, cpb2.y, b2p2.x + b2pvec.x, b2p2.y + b2pvec.y);
      } else {
        // bracket 1
        path.moveTo(b1p1.x + b1pvec.x, b1p1.y + b1pvec.y);
        path.lineTo(b1p1.x, b1p1.y);
        path.lineTo(b1p2.x, b1p2.y);
        path.lineTo(b1p2.x + b1pvec.x, b1p2.y + b1pvec.y);

        // bracket 2
        path.moveTo(b2p1.x + b2pvec.x, b2p1.y + b2pvec.y);
        path.lineTo(b2p1.x, b2p1.y);
        path.lineTo(b2p2.x, b2p2.y);
        path.lineTo(b2p2.x + b2pvec.x, b2p2.y + b2pvec.y);
      }

      result.add(GeneralPath.outlineOf(path, stroke, foreground));

      // work out where to put the suffix labels (e.g. ht/hh/eu) superscript
      // and (e.g. n, xl, c, mix) subscript
      // TODO: could be improved
      double b1MaxX = Math.max(b1p1.x, b1p2.x);
      double b2MaxX = Math.max(b2p1.x, b2p2.x);
      double b1MaxY = Math.max(b1p1.y, b1p2.y);
      double b2MaxY = Math.max(b2p1.y, b2p2.y);

      Point2d subSufPnt = b2p2;
      Point2d supSufPnt = b2p1;
      Vector2d subpvec = b2pvec;

      double bXDiff = b1MaxX - b2MaxX;
      double bYDiff = b1MaxY - b2MaxY;

      if (bXDiff > EQUIV_THRESHOLD || (bXDiff > -EQUIV_THRESHOLD && bYDiff < -EQUIV_THRESHOLD)) {
        subSufPnt = b1p2;
        supSufPnt = b1p1;
        subpvec = b1pvec;
      }

      double xDiff = subSufPnt.x - supSufPnt.x;
      double yDiff = subSufPnt.y - supSufPnt.y;

      if (yDiff > EQUIV_THRESHOLD || (yDiff > -EQUIV_THRESHOLD && xDiff > EQUIV_THRESHOLD)) {
        Point2d tmpP = subSufPnt;
        subSufPnt = supSufPnt;
        supSufPnt = tmpP;
      }

      // subscript/superscript suffix annotation
      if (subscriptSuffix != null && !subscriptSuffix.isEmpty()) {
        TextOutline subscriptOutline =
            leftAlign(
                makeText(subscriptSuffix.toLowerCase(Locale.ROOT), subSufPnt, subpvec, labelScale));
        result.add(GeneralPath.shapeOf(subscriptOutline.getOutline(), foreground));
      }
      if (superscriptSuffix != null && !superscriptSuffix.isEmpty()) {
        TextOutline superscriptOutline =
            leftAlign(
                makeText(
                    superscriptSuffix.toLowerCase(Locale.ROOT), supSufPnt, subpvec, labelScale));
        result.add(GeneralPath.shapeOf(superscriptOutline.getOutline(), foreground));
      }
    }
    return result;
  }