public void expand(boolean animate) {

    if (!truncated) {
      return;
    }

    truncated = false;
    if (!animate) {
      requestLayout();
      return;
    }

    ResizeAnimation expandAnimation =
        new ResizeAnimation(getHeight() + expandedLayout.getHeight() - truncatedLayout.getHeight());

    expandAnimation.setDuration(400);
    expandAnimation.setAnimationListener(
        new Animation.AnimationListener() {
          @Override
          public void onAnimationStart(Animation animation) {
            animating = true;
          }

          @Override
          public void onAnimationEnd(Animation animation) {
            animating = false;
          }

          @Override
          public void onAnimationRepeat(Animation animation) {}
        });

    startAnimation(expandAnimation);
  }
  @SuppressWarnings("NullableProblems")
  @Override
  public boolean onTouchEvent(MotionEvent event) {

    int action = event.getAction();
    int x = (int) event.getX();
    int y = (int) event.getY();

    x += getScrollX();
    y += getScrollY();

    boolean retValue = false;

    switch (action) {
      case MotionEvent.ACTION_DOWN:
        retValue = selectBubble(x, y);
        break;
      case MotionEvent.ACTION_MOVE:
        if (selectedSpan != null) {
          selectedSpan.setPressed(spanContains(selectedSpan, x, y), (Spannable) layout().getText());
          retValue = true;
        }
        break;
      case MotionEvent.ACTION_UP:
        if (listener != null && selectedSpan != null && spanContains(selectedSpan, x, y)) {
          listener.onBubbleClicked(this, selectedSpan);
        }

      case MotionEvent.ACTION_CANCEL:
        if (selectedSpan != null) {
          retValue = true;
          if (-1 != ((Spannable) truncatedLayout.getText()).getSpanEnd(selectedSpan)) {
            selectedSpan.setPressed(false, (Spannable) truncatedLayout.getText());
          }
          if (-1 != ((Spannable) expandedLayout.getText()).getSpanEnd(selectedSpan)) {
            selectedSpan.setPressed(false, (Spannable) expandedLayout.getText());
          }
        }
        selectedSpan = null;
        break;

      default:
        break;
    }

    invalidate();
    return retValue;
  }
  private void reflow(CharSequence s, int where, int before, int after) {
    if (s != mBase) return;

    CharSequence text = mDisplay;
    int len = text.length();

    // seek back to the start of the paragraph

    int find = TextUtils.lastIndexOf(text, '\n', where - 1);
    if (find < 0) find = 0;
    else find = find + 1;

    {
      int diff = where - find;
      before += diff;
      after += diff;
      where -= diff;
    }

    // seek forward to the end of the paragraph

    int look = TextUtils.indexOf(text, '\n', where + after);
    if (look < 0) look = len;
    else look++; // we want the index after the \n

    int change = look - (where + after);
    before += change;
    after += change;

    // seek further out to cover anything that is forced to wrap together

    if (text instanceof Spanned) {
      Spanned sp = (Spanned) text;
      boolean again;

      do {
        again = false;

        Object[] force = sp.getSpans(where, where + after, WrapTogetherSpan.class);

        for (int i = 0; i < force.length; i++) {
          int st = sp.getSpanStart(force[i]);
          int en = sp.getSpanEnd(force[i]);

          if (st < where) {
            again = true;

            int diff = where - st;
            before += diff;
            after += diff;
            where -= diff;
          }

          if (en > where + after) {
            again = true;

            int diff = en - (where + after);
            before += diff;
            after += diff;
          }
        }
      } while (again);
    }

    // find affected region of old layout

    int startline = getLineForOffset(where);
    int startv = getLineTop(startline);

    int endline = getLineForOffset(where + before);
    if (where + after == len) endline = getLineCount();
    int endv = getLineTop(endline);
    boolean islast = (endline == getLineCount());

    // generate new layout for affected text

    StaticLayout reflowed;

    synchronized (sLock) {
      reflowed = sStaticLayout;
      sStaticLayout = null;
    }

    if (reflowed == null) {
      reflowed = new StaticLayout(null);
    } else {
      reflowed.prepare();
    }

    reflowed.generate(
        text,
        where,
        where + after,
        getPaint(),
        getWidth(),
        getTextDirectionHeuristic(),
        getSpacingMultiplier(),
        getSpacingAdd(),
        false,
        true,
        mEllipsizedWidth,
        mEllipsizeAt);
    int n = reflowed.getLineCount();

    // If the new layout has a blank line at the end, but it is not
    // the very end of the buffer, then we already have a line that
    // starts there, so disregard the blank line.

    if (where + after != len && reflowed.getLineStart(n - 1) == where + after) n--;

    // remove affected lines from old layout

    mInts.deleteAt(startline, endline - startline);
    mObjects.deleteAt(startline, endline - startline);

    // adjust offsets in layout for new height and offsets

    int ht = reflowed.getLineTop(n);
    int toppad = 0, botpad = 0;

    if (mIncludePad && startline == 0) {
      toppad = reflowed.getTopPadding();
      mTopPadding = toppad;
      ht -= toppad;
    }
    if (mIncludePad && islast) {
      botpad = reflowed.getBottomPadding();
      mBottomPadding = botpad;
      ht += botpad;
    }

    mInts.adjustValuesBelow(startline, START, after - before);
    mInts.adjustValuesBelow(startline, TOP, startv - endv + ht);

    // insert new layout

    int[] ints;

    if (mEllipsize) {
      ints = new int[COLUMNS_ELLIPSIZE];
      ints[ELLIPSIS_START] = ELLIPSIS_UNDEFINED;
    } else {
      ints = new int[COLUMNS_NORMAL];
    }

    Directions[] objects = new Directions[1];

    for (int i = 0; i < n; i++) {
      ints[START] =
          reflowed.getLineStart(i)
              | (reflowed.getParagraphDirection(i) << DIR_SHIFT)
              | (reflowed.getLineContainsTab(i) ? TAB_MASK : 0);

      int top = reflowed.getLineTop(i) + startv;
      if (i > 0) top -= toppad;
      ints[TOP] = top;

      int desc = reflowed.getLineDescent(i);
      if (i == n - 1) desc += botpad;

      ints[DESCENT] = desc;
      objects[0] = reflowed.getLineDirections(i);

      if (mEllipsize) {
        ints[ELLIPSIS_START] = reflowed.getEllipsisStart(i);
        ints[ELLIPSIS_COUNT] = reflowed.getEllipsisCount(i);
      }

      mInts.insertAt(startline + i, ints);
      mObjects.insertAt(startline + i, objects);
    }

    synchronized (sLock) {
      sStaticLayout = reflowed;
      reflowed.finish();
    }
  }
 @Override
 public BasicElement getElement(final Environment env, final ElementMap item) {
   this.env = env;
   this.elementMaps.push(item);
   final ElementMap elementMap = this.elementMaps.peek();
   final String id = "popup";
   final PopupElement element = new PopupElement();
   element.onCheckOut();
   element.setElementMap(elementMap);
   if (elementMap != null && id != null) {
     elementMap.add(id, element);
   }
   element.setAlign(Alignment9.NORTH_EAST);
   element.setHotSpotPosition(Alignment9.SOUTH_WEST);
   element.setHideOnClick(false);
   element.onAttributesInitialized();
   final StaticLayoutData element2 = new StaticLayoutData();
   element2.onCheckOut();
   element2.setElementMap(elementMap);
   element2.setSize(new Dimension(-2, -2));
   element.addBasicElement(element2);
   element2.onAttributesInitialized();
   element2.onChildrenAdded();
   final Container checkOut = Container.checkOut();
   checkOut.setElementMap(elementMap);
   element.addBasicElement(checkOut);
   checkOut.onAttributesInitialized();
   final StaticLayout element3 = new StaticLayout();
   element3.onCheckOut();
   element3.setAdaptToContentSize(true);
   checkOut.addBasicElement(element3);
   element3.onAttributesInitialized();
   element3.onChildrenAdded();
   final String id2 = "container";
   final Container checkOut2 = Container.checkOut();
   checkOut2.setElementMap(elementMap);
   if (elementMap != null && id2 != null) {
     elementMap.add(id2, checkOut2);
   }
   checkOut2.setStyle("chatBubble");
   checkOut.addBasicElement(checkOut2);
   checkOut2.onAttributesInitialized();
   final StaticLayoutData element4 = new StaticLayoutData();
   element4.onCheckOut();
   element4.setElementMap(elementMap);
   element4.setSize(new Dimension(100.0f, 100.0f));
   checkOut2.addBasicElement(element4);
   element4.onAttributesInitialized();
   element4.onChildrenAdded();
   final DecoratorAppearance appearance = checkOut2.getAppearance();
   appearance.setElementMap(elementMap);
   checkOut2.addBasicElement(appearance);
   appearance.onAttributesInitialized();
   final Margin checkOut3 = Margin.checkOut();
   checkOut3.setElementMap(elementMap);
   checkOut3.setInsets(new Insets(0, 0, 15, 0));
   appearance.addBasicElement(checkOut3);
   checkOut3.onAttributesInitialized();
   checkOut3.onChildrenAdded();
   final Padding element5 = new Padding();
   element5.onCheckOut();
   element5.setElementMap(elementMap);
   element5.setInsets(new Insets(10, 15, 10, 15));
   appearance.addBasicElement(element5);
   element5.onAttributesInitialized();
   element5.onChildrenAdded();
   appearance.onChildrenAdded();
   final TextView element6 = new TextView();
   element6.onCheckOut();
   element6.setElementMap(elementMap);
   element6.setStyle("smallboldMap");
   element6.setMinWidth(1);
   element6.setMaxWidth(200);
   checkOut2.addBasicElement(element6);
   element6.onAttributesInitialized();
   final PropertyElement checkOut4 = PropertyElement.checkOut();
   checkOut4.setElementMap(elementMap);
   checkOut4.setName("mapPopupDescription");
   checkOut4.setAttribute("text");
   element6.addBasicElement(checkOut4);
   checkOut4.onAttributesInitialized();
   checkOut4.onChildrenAdded();
   final PropertyElement checkOut5 = PropertyElement.checkOut();
   checkOut5.setElementMap(elementMap);
   checkOut5.setName("mapPopupIsEditing");
   checkOut5.setAttribute("visible");
   element6.addBasicElement(checkOut5);
   checkOut5.onAttributesInitialized();
   final ConditionResult element7 = new ConditionResult();
   element7.onCheckOut();
   element7.setElementMap(elementMap);
   checkOut5.addBasicElement(element7);
   element7.onAttributesInitialized();
   final FalseCondition element8 = new FalseCondition();
   element8.onCheckOut();
   element8.setElementMap(elementMap);
   element7.addBasicElement(element8);
   element8.onAttributesInitialized();
   element8.onChildrenAdded();
   element7.onChildrenAdded();
   checkOut5.onChildrenAdded();
   element6.onChildrenAdded();
   final String id3 = "textEditor";
   final TextEditor textEditor = new TextEditor();
   textEditor.onCheckOut();
   textEditor.setElementMap(elementMap);
   if (elementMap != null && id3 != null) {
     elementMap.add(id3, textEditor);
   }
   textEditor.setStyle("withoutBorder");
   textEditor.setMaxChars(200);
   textEditor.setMinWidth(200);
   textEditor.setMaxWidth(200);
   final KeyTypedListener onKeyType = new KeyTypedListener();
   onKeyType.setCallBackFunc("wakfu.map:onTextEditorChange");
   textEditor.setOnKeyType(onKeyType);
   final KeyPressedListener onKeyPress = new KeyPressedListener();
   onKeyPress.setCallBackFunc("wakfu.map:onTextEditorKeyPress");
   textEditor.setOnKeyPress(onKeyPress);
   textEditor.setFocusable(true);
   textEditor.setSelectOnFocus(true);
   checkOut2.addBasicElement(textEditor);
   textEditor.onAttributesInitialized();
   final PropertyElement checkOut6 = PropertyElement.checkOut();
   checkOut6.setElementMap(elementMap);
   checkOut6.setName("mapPopupDescription");
   checkOut6.setAttribute("text");
   textEditor.addBasicElement(checkOut6);
   checkOut6.onAttributesInitialized();
   checkOut6.onChildrenAdded();
   final PropertyElement checkOut7 = PropertyElement.checkOut();
   checkOut7.setElementMap(elementMap);
   checkOut7.setName("mapPopupIsEditing");
   checkOut7.setAttribute("visible");
   textEditor.addBasicElement(checkOut7);
   checkOut7.onAttributesInitialized();
   checkOut7.onChildrenAdded();
   final PropertyElement checkOut8 = PropertyElement.checkOut();
   checkOut8.setElementMap(elementMap);
   checkOut8.setName("mapPopupIsEditing");
   checkOut8.setAttribute("focused");
   textEditor.addBasicElement(checkOut8);
   checkOut8.onAttributesInitialized();
   checkOut8.onChildrenAdded();
   textEditor.onChildrenAdded();
   final String id4 = "valid";
   final Button button = new Button();
   button.onCheckOut();
   button.setElementMap(elementMap);
   if (elementMap != null && id4 != null) {
     elementMap.add(id4, button);
   }
   button.setStyle("smallValid");
   final MouseClickedListener onClick = new MouseClickedListener();
   onClick.setCallBackFunc("wakfu.map:applyNote");
   button.setOnClick(onClick);
   checkOut2.addBasicElement(button);
   button.onAttributesInitialized();
   final PropertyElement checkOut9 = PropertyElement.checkOut();
   checkOut9.setElementMap(elementMap);
   checkOut9.setName("mapPopupIsEditing");
   checkOut9.setAttribute("visible");
   button.addBasicElement(checkOut9);
   checkOut9.onAttributesInitialized();
   checkOut9.onChildrenAdded();
   button.onChildrenAdded();
   checkOut2.onChildrenAdded();
   final String id5 = "image";
   final Image image = new Image();
   image.onCheckOut();
   image.setElementMap(elementMap);
   if (elementMap != null && id5 != null) {
     elementMap.add(id5, image);
   }
   image.setStyle("BubbleArrowLeft");
   image.setNonBlocking(true);
   checkOut.addBasicElement(image);
   image.onAttributesInitialized();
   final StaticLayoutData element9 = new StaticLayoutData();
   element9.onCheckOut();
   element9.setElementMap(elementMap);
   element9.setAlign(Alignment17.SOUTH_WEST);
   element9.setSize(new Dimension(-2, -2));
   image.addBasicElement(element9);
   element9.onAttributesInitialized();
   element9.onChildrenAdded();
   image.onChildrenAdded();
   checkOut.onChildrenAdded();
   element.onChildrenAdded();
   return element;
 }
  private void build(int width) {

    positions.clear();
    width = width - getPaddingLeft() - getPaddingRight();

    if (width <= 0 || TextUtils.isEmpty(text)) {
      truncatedLayout = expandedLayout = null;
      return;
    }

    for (BubbleSpan span : spans) {
      span.resetWidth(width);
    }

    try {

      truncatedLayout =
          expandedLayout =
              new StaticLayout(
                  text, textPaint, width, Layout.Alignment.ALIGN_NORMAL, lineSpacing, 1, false);

      if (maxLines > 0 && truncatedLayout.getLineCount() > maxLines) {

        int lineEnd = truncatedLayout.getLineEnd(maxLines - 1);
        int offset = -1;
        StaticLayout sl =
            new StaticLayout(
                moreText, textPaint, width, Layout.Alignment.ALIGN_NORMAL, lineSpacing, 1, false);

        sl.getWidth();
        while (truncatedLayout.getLineCount() > maxLines && lineEnd > 0) {

          if (offset == -1
              && truncatedLayout.getLineWidth(maxLines - 1) + sl.getLineWidth(0) > width) {

            offset =
                truncatedLayout.getOffsetForHorizontal(maxLines - 1, width - sl.getLineWidth(0));

            lineEnd = offset;

          } else if (offset > 0) {
            lineEnd--;
          }

          SpannableStringBuilder textTruncated =
              new SpannableStringBuilder(text.subSequence(0, lineEnd));
          textTruncated.append(moreText);

          truncatedLayout =
              new StaticLayout(
                  textTruncated,
                  textPaint,
                  width,
                  Layout.Alignment.ALIGN_NORMAL,
                  lineSpacing,
                  1,
                  false);
        }
      }
    } catch (java.lang.ArrayIndexOutOfBoundsException e) {
      return;
    }

    if (truncated) {
      recomputeSpans((Spannable) truncatedLayout.getText());
    } else {
      recomputeSpans((Spannable) expandedLayout.getText());
    }

    for (BubbleSpan span : spans) {
      positions.put(span, span.rect(this));
    }
  }
Example #6
0
    public void build(MessageWireframe wireframe, int desiredWidth, StelsApplication application) {

      Logger.d(TAG, "Build layout start");

      checkResources(application);

      senderPaint = initTextPaint();
      senderPaint.setTypeface(FontController.loadTypeface(application, "regular"));
      senderPaint.setTextSize(bodyPaint.getTextSize());
      senderPaint.setColor(0xff000000);

      forwardingPaint = initTextPaint();
      forwardingPaint.setTypeface(FontController.loadTypeface(application, "light"));
      forwardingPaint.setTextSize(bodyPaint.getTextSize());
      forwardingPaint.setColor(0xff000000);

      this.layoutDesiredWidth = desiredWidth;
      this.isOut = wireframe.message.isOut();
      this.showState = isOut;
      this.isGroup = wireframe.message.getPeerType() == PeerType.PEER_CHAT && !isOut;
      if (isGroup) {
        User user = wireframe.senderUser;
        this.senderName = user.getDisplayName();
        if (!wireframe.message.isForwarded()) {
          senderPaint.setColor(
              Placeholders.USER_PLACEHOLDERS_COLOR[
                  wireframe.message.getSenderId() % Placeholders.USER_PLACEHOLDERS_COLOR.length]);
          forwardingPaint.setColor(
              Placeholders.USER_PLACEHOLDERS_COLOR[
                  wireframe.message.getSenderId() % Placeholders.USER_PLACEHOLDERS_COLOR.length]);
        }
      }

      if (wireframe.message.isForwarded()) {
        isForwarded = true;
        this.forwarderName = wireframe.forwardUser.getDisplayName();
        if (isOut) {
          forwardingPaint.setColor(0xff739f53);
          senderPaint.setColor(0xff739f53);
        } else {
          forwardingPaint.setColor(0xff4884cf);
          senderPaint.setColor(0xff4884cf);
        }
      } else {
        isForwarded = false;
      }

      if (isGroup) {
        User user = application.getEngine().getUser(wireframe.message.getSenderId());
        this.senderName = user.getDisplayName();
      }
      if (wireframe.message.isForwarded()) {
        isForwarded = true;
        this.forwarderName = wireframe.forwardUser.getDisplayName();
      } else {
        isForwarded = false;
      }

      layoutDesiredWidth = desiredWidth;
      long start = SystemClock.uptimeMillis();

      this.spannable =
          application
              .getEmojiProcessor()
              .processEmojiCompatMutable(
                  wireframe.message.getMessage(), EmojiProcessor.CONFIGURATION_BUBBLES);

      // spannable = new SpannableString(wireframe.message.getMessage());
      Logger.d(TAG, "Emoji processed in " + (SystemClock.uptimeMillis() - start) + " ms");
      start = SystemClock.uptimeMillis();
      Linkify.addLinks(
          this.spannable, Linkify.WEB_URLS | Linkify.PHONE_NUMBERS | Linkify.EMAIL_ADDRESSES);
      fixLinks(spannable);
      Logger.d(TAG, "Added links in " + (SystemClock.uptimeMillis() - start) + " ms");
      start = SystemClock.uptimeMillis();
      layout =
          new StaticLayout(
              spannable, bodyPaint, desiredWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, true);
      Logger.d(TAG, "Built base layout in " + (SystemClock.uptimeMillis() - start) + " ms");

      if (layout.getLineCount() < 20) {
        int layoutTextWidth = 0;

        for (int i = 0; i < layout.getLineCount(); i++) {
          layoutTextWidth = (int) Math.max(layout.getLineWidth(i), layoutTextWidth);
        }

        if (layoutTextWidth < layout.getWidth() - px(10)) {
          layout =
              new StaticLayout(
                  spannable,
                  bodyPaint,
                  layoutTextWidth + px(2),
                  Layout.Alignment.ALIGN_NORMAL,
                  1.0f,
                  0.0f,
                  true);
        }
      }

      layoutRealWidth = layout.getWidth();

      timeWidth = (int) clockOutPaint.measureText(wireframe.date) + px((showState ? 23 : 0) + 6);

      if (layout.getLineCount() == 1) {
        boolean isLastRtl = layout.getParagraphDirection(0) == Layout.DIR_RIGHT_TO_LEFT;
        if (!isLastRtl && desiredWidth - layoutRealWidth > timeWidth) {
          layoutRealWidth += timeWidth;
          layoutHeight = layout.getHeight() + px(3);
        } else if (isLastRtl && desiredWidth - layout.getWidth() > timeWidth) {
          layoutRealWidth = layout.getWidth() + timeWidth;
          layoutHeight = layout.getHeight() + px(3);
        } else {
          if (isLastRtl) {
            layoutRealWidth = layout.getWidth();
          }

          layoutHeight = layout.getHeight() + px(17);
        }
      } else {
        boolean isLastRtl =
            layout.getParagraphDirection(layout.getLineCount() - 1) == Layout.DIR_RIGHT_TO_LEFT;
        if (!isLastRtl
            && (desiredWidth - layout.getLineWidth(layout.getLineCount() - 1) > timeWidth)) {
          layoutRealWidth =
              (int)
                  Math.max(
                      layoutRealWidth, layout.getLineWidth(layout.getLineCount() - 1) + timeWidth);
          layoutHeight = layout.getHeight() + px(3);
        } else if (isLastRtl && (desiredWidth - layout.getWidth() > timeWidth)) {
          layoutRealWidth = (int) Math.max(layoutRealWidth, layout.getWidth() + timeWidth);
          layoutHeight = layout.getHeight() + px(3);
        } else {
          layoutHeight = layout.getHeight() + px(17);
        }
      }

      if (layoutRealWidth < timeWidth) {
        layoutRealWidth = timeWidth;
      }

      if (isForwarded) {
        layoutHeight += px(19) * 2;
        forwardOffset = (int) forwardingPaintBase.measureText("From ");
        layoutRealWidth =
            (int) Math.max(layoutRealWidth, forwardingPaintBase.measureText("Forwarded message"));
        forwarderNameMeasured =
            TextUtils.ellipsize(
                    forwarderName,
                    senderPaintBase,
                    desiredWidth - forwardOffset,
                    TextUtils.TruncateAt.END)
                .toString();
        layoutRealWidth =
            (int)
                Math.max(
                    layoutRealWidth,
                    forwardOffset + senderPaintBase.measureText(forwarderNameMeasured));
      }

      if (isGroup && !isOut && !isForwarded) {
        layoutHeight += px(19);
        senderNameMeasured =
            TextUtils.ellipsize(senderName, senderPaintBase, desiredWidth, TextUtils.TruncateAt.END)
                .toString();
        int width = (int) senderPaintBase.measureText(senderNameMeasured);
        layoutRealWidth = Math.max(layoutRealWidth, width);
      }

      Logger.d(TAG, "Build layout end");
    }
  public List<Integer> getPageOffsets(CharSequence text, boolean includePageNumbers) {

    if (text == null) {
      return new ArrayList<Integer>();
    }

    List<Integer> pageOffsets = new ArrayList<Integer>();

    TextPaint textPaint = bookView.getInnerView().getPaint();
    int boundedWidth = bookView.getInnerView().getWidth();

    LOG.debug("Page width: " + boundedWidth);

    StaticLayout layout =
        layoutFactory.create(text, textPaint, boundedWidth, bookView.getLineSpacing());
    LOG.debug("Layout height: " + layout.getHeight());

    layout.draw(new Canvas());

    // Subtract the height of the top margin
    int pageHeight = bookView.getHeight() - bookView.getVerticalMargin();

    if (includePageNumbers) {
      String bottomSpace = "0\n";

      StaticLayout numLayout =
          layoutFactory.create(bottomSpace, textPaint, boundedWidth, bookView.getLineSpacing());
      numLayout.draw(new Canvas());

      // Subtract the height needed to show page numbers, or the
      // height of the margin, whichever is more
      pageHeight = pageHeight - Math.max(numLayout.getHeight(), bookView.getVerticalMargin());
    } else {
      // Just subtract the bottom margin
      pageHeight = pageHeight - bookView.getVerticalMargin();
    }

    LOG.debug("Got pageHeight " + pageHeight);

    int totalLines = layout.getLineCount();
    int topLineNextPage = -1;
    int pageStartOffset = 0;

    while (topLineNextPage < totalLines - 1) {

      LOG.debug("Processing line " + topLineNextPage + " / " + totalLines);

      int topLine = layout.getLineForOffset(pageStartOffset);
      topLineNextPage = layout.getLineForVertical(layout.getLineTop(topLine) + pageHeight);

      LOG.debug("topLine " + topLine + " / " + topLineNextPage);
      if (topLineNextPage == topLine) { // If lines are bigger than can fit on a page
        topLineNextPage = topLine + 1;
      }

      int pageEnd = layout.getLineEnd(topLineNextPage - 1);

      LOG.debug("pageStartOffset=" + pageStartOffset + ", pageEnd=" + pageEnd);

      if (pageEnd > pageStartOffset) {
        if (text.subSequence(pageStartOffset, pageEnd).toString().trim().length() > 0) {
          pageOffsets.add(pageStartOffset);
        }
        pageStartOffset = layout.getLineStart(topLineNextPage);
      }
    }

    return pageOffsets;
  }