private float calcReferenceHeight(float hCenter) {
    float yBefore = origRectangle.getTop();

    ColumnTextBuilder tempCtb = b.newColumnTextBuilder();

    singleColumnRect.copyPositionsFrom(origRectangle);

    singleColumnRect.setBottom(-100000f);
    singleColumnRect.setRight(hCenter);

    applyPadding(singleColumnRect, true);

    tempCtb.setSimpleColumn(singleColumnRect.get());

    final DirectContentAdder adder = new DirectContentAdder(tempCtb);

    adder
        .setStartWith(initialLeftCTB)
        .setStartAtIndex(startAtElement)
        .setSimulate(true)
        .setHeights(true)
        .go();

    sequence.initialContentHeight = adder.startContentHeight;

    float yAfter = tempCtb.getYLine();

    return (yBefore - yAfter);
  }
  private ColumnTextBuilder setColumn(
      float height,
      float horCenter,
      boolean isLeft,
      boolean simulate,
      RectangleBuilder singleColumnRect,
      ColumnTextBuilder ctb) {
    //        new RectangleBuilder().
    final RectangleBuilder rect =
        b.newCopyRectangleBuilder(origRectangle.get())
            .setBottom(singleColumnRect.getTop() - height);

    if (isLeft) {
      rect.setRight(horCenter);
    } else {
      rect.setLeft(horCenter);
    }

    applyPadding(rect, isLeft);

    logger.debug("setting column to: {}", rect);

    ctb.setSimpleColumn(rect.get());

    return ctb;
  }
  public BalancedColumnsBuilder(
      ColumnTextBuilder initialLeftCTB, int startAtElement, Rectangle rectangle, ITextBuilder b) {
    this.initialLeftCTB = initialLeftCTB.freeze();
    this.startAtElement = startAtElement;

    this.origRectangle = new RectangleBuilder().reuse(rectangle);
    this.b = b;
  }
  private AtomicIncreaseResult iterateOnRight(
      int currentIndex,
      SplitResult currentResult,
      float elementTop,
      Iterator<AtomicIncreaseResult> iterator) {
    AtomicIncreaseResult lastIncreaseResult = null;

    currentResult
        .setRightColumnHeight(rightCTB.getCurrentHeight())
        .setRightElementSplitHeight(
            elementTop - rightCTB.getYLine(), sequence.getHeight(currentIndex));

    bestResult.assignIfWorseThan(currentResult);

    cycle:
    while (iterator.hasNext()) {
      AtomicIncreaseResult r = iterator.next();
      currentResult.setRightColumnHeight(rightCTB.getCurrentHeight());

      lastIncreaseResult = r;

      switch (r.type) {
        case PAGE_OVERFLOW:
          break cycle;
        case NORMAL:
          currentResult.setRightElementSplitHeight(
              elementTop - rightCTB.getYLine(), sequence.getHeight(currentIndex));
          bestResult.assignIfWorseThan(currentResult);
          break;
        case NO_MORE_CONTENT:
          // must be done in the main cycle
          //                    setFullAddedElementsStateRight(currentResult, currentIndex + 1,
          // rightCTB.getCurrentHeight());

          break cycle;
      }
    }
    return lastIncreaseResult == null
        ? new AtomicIncreaseResult(0, ColumnTextBuilder.GrowthResultType.NO_MORE_CONTENT)
        : lastIncreaseResult;
  }
  private BalancingResult applyBestResult() {
    final float hCenter = horCenter();
    List<Element> elements = sequence.getElements();

    setColumn((float) bestResult.leftColumnHeight, hCenter, true, true, singleColumnRect, leftCTB);
    setColumn(
        (float) bestResult.rightColumnHeight, hCenter, false, false, singleColumnRect, rightCTB);

    if (b.drawBorders) {
      b.getCanvasBuilder().drawGrayRectangle(leftCTB.getSimpleColumnRectangle(), BaseColor.RED);
      b.getCanvasBuilder().drawGrayRectangle(rightCTB.getSimpleColumnRectangle(), BaseColor.GREEN);
    }

    leftCTB.clearContent();
    rightCTB.clearContent();

    final DirectContentAdder.Result addResult =
        new DirectContentAdder(leftCTB)
            .setStartWith(initialLeftCTB)
            .setStartAtIndex(startAtElement)
            .setSwitchToRightCTB(rightCTB)
            .setSimulate(false)
            .go();

    float yLine = Math.min(leftCTB.getYLine(), rightCTB.getYLine());

    final BalancingResult r;

    if (addResult.hasContentLeft(elements.size())) {
      final ColumnTextBuilder contentCopy =
          addResult.contentLeft == null
              ? null
              : b.newColumnTextBuilder().setACopy(addResult.contentLeft);

      r = startWithANewPage(contentCopy, addResult.index);
    } else {
      r = new BalancingResult(yLine);

      //            if(updateAfterRun != null){
      //                updateAfterRun.growBottom(origRectangle.getTop() - yLine);
      //            }
    }

    if (updateAfterRun != null) {
      updateAfterRun.setSimpleColumn(
          b.reuseRectangleBuilder(new Rectangle(origRectangle.get()))
              .setTop(yLine)
              .setBottom(b.getDocument().bottom())
              .get());
    }

    return r;
  }
  private boolean iterateOnLeft(
      Iterator<AtomicIncreaseResult> iterator,
      int currentIndex,
      SplitResult currentResult,
      float elementTop,
      float elementHeight) {
    currentResult
        .setLeftColumnHeight(leftCTB.getCurrentHeight())
        .setLeftElementSplitHeight(elementTop - leftCTB.getYLine(), elementHeight);

    if (currentResult.leftElementSplitHeight > 0) {
      considerAddingToRight(currentIndex + 1, true);
    }

    while (iterator.hasNext()) {
      AtomicIncreaseResult r = iterator.next();

      currentResult
          .setLeftColumnHeight(leftCTB.getCurrentHeight())
          .setLeftElementSplitHeight(elementTop - leftCTB.getYLine(), elementHeight);

      switch (r.type) {
        case PAGE_OVERFLOW:
          return true;

        case NORMAL:
          considerAddingToRight(currentIndex + 1, true);
          break;

        case NO_MORE_CONTENT:
          // don't consider adding to right as this case must be checked in the main loop
          //                    considerAddingToRight(currentIndex + 1, false);
          break;
      }
    }
    return false;
  }
  private BalancingResult startWithANewPage(ColumnTextBuilder content, int startAt) {
    //noinspection SimplifiableConditionalExpression
    logger.debug(
        "starting with a new page, content: {}, startAt: {}",
        content == null ? false : content.hasMoreText(),
        startAt);

    final RectangleBuilder rect =
        b.reuseRectangleBuilder(origRectangle.get())
            .setTop(b.getDocument().top())
            .setBottom(b.getDocument().bottom());

    b.getDocument().newPage();

    return new BalancedColumnsBuilder(content, startAt, rect.get(), b).setSequence(sequence).go();
  }
 public BalancedColumnsBuilder setColumnText(ColumnTextBuilder ctb, ITextBuilder b) {
   this.origRectangle = new RectangleBuilder().reuse(ctb.getCurrentRectangle().get());
   updateAfterRun = ctb;
   this.b = b;
   return this;
 }
  private void considerAddingToRight(int startAt, boolean hasNotFlushedText) {
    logger.trace(
        "adding to right, i: {}, leftHeight: {}, leftSplit: {}",
        startAt,
        currentLeftResult.leftColumnHeight,
        currentLeftResult.leftElementSplitHeight);

    // copy content from the left
    rightCTB.setACopy(leftCTB).setYLine(leftCTB.getTop());

    if (!hasNotFlushedText && sequence.isSpace(startAt)) {
      startAt++;
    }

    // we can flush what's left from the previous column
    if (rightCTB.hasMoreText()) {
      rightCTB.go(true);
    }

    currentRightResult.copyFrom(currentLeftResult);

    // copied from left method
    int i;

    List<Element> elements = sequence.getElements();

    final DirectContentAdder.Result quickResult =
        new DirectContentAdder(rightCTB)
            .setStartWith(leftCTB)
            .setStartAtIndex(startAt)
            .setQuickHeight(leftCTB.getCurrentHeight())
            .setSimulate(true)
            .go();

    i = quickResult.index;

    int elementsAdded = quickResult.contentLeft == null ? i : i - 1;

    bestResult.assignIfWorseThan(
        currentRightResult
            .setElementsAddedCount(elementsAdded) // todo!!!
            .setRightElementSplitHeight(0, 0)
            .setRightColumnHeight(rightCTB.getCurrentHeight())
            .setPageSplit(elementsAdded, quickResult.hasContentLeft(elements.size())));

    boolean pageOverFlow = false;

    if (quickResult.contentLeft != null) {
      rightCTB.copyContentFrom(quickResult.contentLeft);
      AtomicIncreaseResult lastResult =
          iterateOnRight(
              i - 1, currentRightResult, rightCTB.getTop(), rightCTB.newAtomicIteratorFor());
      if (lastResult.type == ColumnTextBuilder.GrowthResultType.PAGE_OVERFLOW) {
        pageOverFlow = true;
      }
    }

    if (i == elements.size()) {
      bestResult.assignIfWorseThan(
          currentRightResult
              .setRightElementSplitHeight(0, 0)
              .setRightColumnHeight(rightCTB.getCurrentHeight())
              .setElementsAddedCount(i)
              .setPageSplit(i, rightCTB.hasMoreText()));
    }

    if (pageOverFlow) return;

    elementsCycle:
    for (; i < elements.size(); i++) {
      Element el = elements.get(i);

      final SplitResult currentResult = currentRightResult;

      setFullAddedElementsStateRight(currentResult, i, rightCTB.getCurrentHeight());

      currentResult.setPageSplit(true); // temporary pessimism

      if (el instanceof SpaceElement) {
        // todo extract method
        SpaceElement space = (SpaceElement) el;

        bestResult.assignIfWorseThan(currentResult.setElementsAddedCount(i).setPageSplit(i, false));

        if (space.fits(rightCTB, origRectangle.getBottom())) {
          space.add(rightCTB, true);
        } else {
          if (rightCTB.getSimpleColumnRectangle().getBottom() - space.getHeight()
              < b.getDocument().bottom()) {
            break;
          }

          rightCTB.growBottom(space.getHeight()).setYLine(rightCTB.getYLine() - space.getHeight());
        }
      } else {
        float elementTop = rightCTB.getYLine();

        final Iterator<AtomicIncreaseResult> iterator = rightCTB.newAtomicIteratorFor(el);

        AtomicIncreaseResult lastResult = iterateOnRight(i, currentResult, elementTop, iterator);

        if (lastResult.type == ColumnTextBuilder.GrowthResultType.PAGE_OVERFLOW) {
          break;
        }
      }

      // element is fully added here
      currentResult
          .setRightColumnHeight(rightCTB.getCurrentHeight())
          .setRightElementSplitHeight(0, 0);

      bestResult.assignIfWorseThan(currentResult);
    }
  }
  private BalancingResult _go() {
    if (b.drawBorders) {
      b.getCanvasBuilder().drawGrayRectangle(origRectangle.get(), BaseColor.LIGHT_GRAY);
    }

    currentLeftResult.totalElementCount =
        currentRightResult.totalElementCount = bestResult.totalElementCount = sequence.size();

    // try adding into a single infinite column to calc height
    final float hCenter = horCenter();

    referenceHeight = calcReferenceHeight(hCenter);

    currentLeftResult.referenceHeight =
        currentRightResult.referenceHeight = bestResult.referenceHeight = referenceHeight;

    leftCTB =
        setColumn(
            (float) referenceHeight,
            hCenter,
            true,
            true,
            singleColumnRect,
            b.newColumnTextBuilder());
    rightCTB =
        setColumn(
            (float) referenceHeight,
            hCenter,
            false,
            true,
            singleColumnRect,
            b.newColumnTextBuilder());

    minimalLeftColumnHeight = MINIMAL_HEIGHT_COEFFICIENT * referenceHeight / 2;

    int i;

    List<Element> elements = sequence.getElements();

    final DirectContentAdder.Result quickResult =
        new DirectContentAdder(leftCTB)
            .setStartWith(initialLeftCTB)
            .setStartAtIndex(startAtElement)
            .setQuickHeight(minimalLeftColumnHeight)
            .setSimulate(true)
            .go();

    i = quickResult.index;

    bestResult.assignIfWorseThan(
        currentLeftResult
            .setElementsAddedCount(i)
            .setLeftElementSplitHeight(0, 0)
            .setLeftColumnHeight(leftCTB.getCurrentHeight())
            .setPageSplit(i, quickResult.hasContentLeft(elements.size())));

    boolean pageOverFlow = false;

    // the only situation possible is the content left from initialContent
    if (quickResult.contentLeft != null) {
      leftCTB.copyContentFrom(quickResult.contentLeft);
      pageOverFlow =
          iterateOnLeft(
              leftCTB.newAtomicIteratorFor(),
              i - 1,
              currentLeftResult,
              leftCTB.getTop(),
              sequence.initialContentHeight);
    }

    if (i == elements.size()) {
      bestResult.assignIfWorseThan(
          currentLeftResult
              .setLeftElementSplitHeight(0, 0)
              .setLeftColumnHeight(leftCTB.getCurrentHeight())
              .setElementsAddedCount(i)
              .setPageSplit(i, leftCTB.hasMoreText()));
    }

    if (pageOverFlow) {
      return applyBestResult();
    }

    elementsCycle:
    for (; i < elements.size(); i++) {
      Element el = elements.get(i);
      final SplitResult currentResult = currentLeftResult;

      currentResult
          .setLeftElementSplitHeight(0, 0)
          .setElementsAddedCount(i)
          .setLeftColumnHeight(leftCTB.getCurrentHeight());

      considerAddingOnRightAtWholeElement(i);

      if (el instanceof SpaceElement) {
        SpaceElement space = (SpaceElement) el;

        currentResult.setElementsAddedCount(i);

        considerAddingToRight(i + 1, false);

        if (space.fits(leftCTB, origRectangle.getBottom())) {
          space.add(leftCTB, true);
        } else {
          if (leftCTB.getSimpleColumnRectangle().getBottom() - space.getHeight()
              < b.getDocument().bottom()) {
            break;
          }

          leftCTB.growBottom(space.getHeight()).setYLine(leftCTB.getYLine() - space.getHeight());
        }
      } else {
        float elementTop = leftCTB.getYLine();

        final Iterator<AtomicIncreaseResult> iterator = leftCTB.newAtomicIteratorFor(el);

        if (iterateOnLeft(iterator, i, currentResult, elementTop, sequence.getHeight(i))) {
          break elementsCycle;
        }

        //                leftCTB.restoreState();
      }

      currentResult.setLeftColumnHeight(leftCTB.getCurrentHeight()).setElementsAddedCount(i + 1);
    }

    considerAddingOnRightAtWholeElement(i);

    // here we have bestResult, so let's add our content with no simulation!

    //        setColumn(bestResult, b.newColumnTextBuilder())
    return applyBestResult();
  }
    public Result go() {
      if (quickHeight != 0) {
        if (rightCTB != null) {
          throw new IllegalStateException("if quickHeight !=0 then rightCTB == null!");
        }
      }

      Preconditions.checkNotNull(dest);

      ColumnTextBuilder currentCtb = dest;
      List<Element> elements = sequence.getElements();

      int i;

      if (quickHeight != 0) {
        currentCtb.adjustBottom((float) quickHeight);
      }

      if (startWith != null) {
        currentCtb.copyContentFrom(startWith);

        float yBefore = currentCtb.getYLine();

        int status = currentCtb.go(simulate);

        if (setHeights) {
          startContentHeight = yBefore - currentCtb.getYLine();
        }

        if (ColumnText.hasMoreText(status)) {
          if (rightCTB != null) { // => quickHeight == 0
            rightCTB.copyContentFrom(currentCtb);

            currentCtb = rightCTB;

            yBefore = currentCtb.getYLine();

            status = currentCtb.go(simulate);

            if (setHeights) {
              startContentHeight += yBefore - currentCtb.getYLine();
            }

            if (ColumnText.hasMoreText(status)) {
              return new Result(ColumnText.NO_MORE_COLUMN, startAtIndex, currentCtb);
            }
          } else {
            return new Result(ColumnText.NO_MORE_COLUMN, startAtIndex, currentCtb);
          }
        }
      }

      // optimisation mode
      if (quickHeight != 0) {
        for (i = startAtIndex; i < elements.size(); i++) {
          Element el = elements.get(i);

          if (currentCtb.fits(el)) {
            if (el instanceof SpaceElement) {
              SpaceElement spaceElement = (SpaceElement) el;
              spaceElement.add(currentCtb, simulate);
              currentCtb.go(simulate);
            } else {
              currentCtb.addElement(el).go(simulate);
            }
          } else {
            break;
          }
        }

        return new Result(ColumnText.NO_MORE_TEXT, i, null);
      }

      for (i = startAtElement; i < elements.size(); i++) {
        Element el = elements.get(i);

        float yBefore = currentCtb.getYLine();

        if (el instanceof SpaceElement) {
          SpaceElement space = (SpaceElement) el;

          if (space.fits(currentCtb, currentCtb.getBottom())) {
            space.add(currentCtb, simulate);
          } else {
            if (currentCtb == dest) {
              currentCtb = rightCTB;
            } else if (currentCtb == rightCTB) {
              return new Result(ColumnText.NO_MORE_COLUMN, i + 1, currentCtb.clearContent());
            }
          }
        } else {
          currentCtb.addElement(el);

          if (ColumnText.hasMoreText(currentCtb.go(simulate))) {
            if (currentCtb == dest) {
              if (rightCTB == null) {
                return new Result(ColumnText.NO_MORE_COLUMN, i + 1, currentCtb);
              } else {
                rightCTB.copyContentFrom(currentCtb);

                currentCtb = rightCTB;

                final int status = currentCtb.go(simulate);

                if (ColumnText.hasMoreText(status)) {
                  return new Result(ColumnText.NO_MORE_COLUMN, i + 1, currentCtb);
                }
              }
            } else {
              return new Result(ColumnText.NO_MORE_COLUMN, i + 1, currentCtb);
            }
          }
        }

        if (setHeights) {
          sequence.setHeight(i, yBefore - currentCtb.getYLine());
        }
      }

      return new Result(ColumnText.NO_MORE_TEXT, elements.size(), null);
    }