content: /*
   * Copyright (C) 2015-2023 Emanuel Moecklin
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.onegravity.rteditor.utils;
  
  import android.text.Spanned;
  
  import java.io.Serializable;
  import java.util.ArrayList;
  import java.util.regex.Matcher;
  import java.util.regex.Pattern;
  
  /**
   * This class finds the Paragraphs in a Spanned text.
   *
   * A paragraph spans from one \n (exclusive) to the next one (inclusive):
   * first paragraph\nsecond paragraph\nthird paragraph
   * |_______________||________________||_____________|
   *
   * We need this for all the paragraph formatting (bullet points, indentation etc.).
   * While it's optimized for performance, it should still be used with caution.
   */
  public class RTLayout implements Serializable {
      private static final long serialVersionUID = 2210969820444215580L;
  
      private static final Pattern LINEBREAK_PATTERN = Pattern.compile("\\r\\n|\\r|\\n");
  
      private int mNrOfLines = 0;
      private final ArrayList<Paragraph> mParagraphs = new ArrayList<>();
  
      public RTLayout(final Spanned spanned) {
          if (spanned != null) {
              final String s = spanned.toString();
  
              // find the line breaks and the according lines / paragraphs
              mNrOfLines = 1;
              final Matcher m = LINEBREAK_PATTERN.matcher(s);
              int groupStart = 0;
              while (m.find()) {
                  // the line feeds are part of the paragraph              isFirst          isLast
                  Paragraph paragraph = new Paragraph(groupStart, m.end(), mNrOfLines == 1, false);
                  mParagraphs.add(paragraph);
                  groupStart = m.end();
                  mNrOfLines++;
              }
  
              // even an empty line after the last cr/lf is considered a paragraph
              if (mParagraphs.size() < mNrOfLines) {
                  Paragraph paragraph = new Paragraph(groupStart, s.length(), mNrOfLines == 1, true);
                  mParagraphs.add(paragraph);
              }
          }
      }
  
      /**
       * @return all Paragraphs for this layout / spanned text.
       */
      public ArrayList<Paragraph> getParagraphs() {
          return mParagraphs;
      }
  
      /**
       * @return the line for a certain position in the spanned text
       */
      public int getLineForOffset(final int offset) {
          int lineNr = 0;
          while (lineNr < mNrOfLines && offset >= mParagraphs.get(lineNr).end()) {
              lineNr++;
          }
          return Math.min(Math.max(0, lineNr), mParagraphs.size() - 1);
      }
  
      /**
       * @return the start position of a certain line in the spanned text
       */
      public int getLineStart(final int line) {
          return mNrOfLines == 0 || line < 0 ? 0 :
                 line < mNrOfLines ? mParagraphs.get(line).start() :
                 mParagraphs.get(mNrOfLines - 1).end() - 1;
      }
  
      /**
       * @return the end position of a certain line in the spanned text
       */
      public int getLineEnd(final int line) {
          return mNrOfLines == 0 || line < 0 ? 0 :
                 line < mNrOfLines ? mParagraphs.get(line).end() :
                 mParagraphs.get(mNrOfLines - 1).end() - 1;
      }
  
      @Override
      public String toString() {
          StringBuilder s = new StringBuilder();
          int line = 1;
          for (Paragraph p : mParagraphs) {
              s.append(line++).append(": ").append(p.start()).append("-").append(p.end())
                      .append(p.isLast() ? "" : ", ");
          }
          return s.toString();
      }
  }
  