content: /*
   * Copyright (C) 2018 The Android Open Source Project
   *
   * 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 android.graphics.text;
  
  import android.annotation.FloatRange;
  import android.annotation.IntDef;
  import android.annotation.IntRange;
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.annotation.Px;
  
  import dalvik.annotation.optimization.CriticalNative;
  import dalvik.annotation.optimization.FastNative;
  
  import libcore.util.NativeAllocationRegistry;
  
  import java.lang.annotation.Retention;
  import java.lang.annotation.RetentionPolicy;
  
  /**
   * Provides automatic line breaking for a <em>single</em> paragraph.
   *
   * <p>
   * <pre>
   * <code>
   * Paint paint = new Paint();
   * Paint bigPaint = new Paint();
   * bigPaint.setTextSize(paint.getTextSize() * 2.0);
   * String text = "Hello, Android.";
   *
   * // Prepare the measured text
   * MeasuredText mt = new MeasuredText.Builder(text.toCharArray())
   *     .appendStyleRun(paint, 7, false)  // Use paint for "Hello, "
   *     .appednStyleRun(bigPaint, 8, false)  // Use bigPaint for "Hello, "
   *     .build();
   *
   * LineBreaker lb = new LineBreaker.Builder()
   *     // Use simple line breaker
   *     .setBreakStrategy(LineBreaker.BREAK_STRATEGY_SIMPLE)
   *     // Do not add hyphenation.
   *     .setHyphenationFrequency(LineBreaker.HYPHENATION_FREQUENCY_NONE)
   *     // Build the LineBreaker
   *     .build();
   *
   * ParagraphConstraints c = new ParagraphConstraints();
   * c.setWidth(240);  // Set the line wieth as 1024px
   *
   * // Do the line breaking
   * Result r = lb.computeLineBreaks(mt, c, 0);
   *
   * // Compute the total height of the text.
   * float totalHeight = 0;
   * for (int i = 0; i < r.getLineCount(); ++i) {  // iterate over the lines
   *    totalHeight += r.getLineDescent(i) - r.getLineAscent(i);
   * }
   *
   * // Draw text to the canvas
   * Bitmap bmp = Bitmap.createBitmap(240, totalHeight, Bitmap.Config.ARGB_8888);
   * Canvas c = new Canvas(bmp);
   * float yOffset = 0f;
   * int prevOffset = 0;
   * for (int i = 0; i < r.getLineCount(); ++i) {  // iterate over the lines
   *     int nextOffset = r.getLineBreakOffset(i);
   *     c.drawText(text, prevOffset, nextOffset, 0f, yOffset, paint);
   *
   *     prevOffset = nextOffset;
   *     yOffset += r.getLineDescent(i) - r.getLineAscent(i);
   * }
   * </code>
   * </pre>
   * </p>
   */
  public class LineBreaker {
      /** @hide */
      @IntDef(prefix = { "BREAK_STRATEGY_" }, value = {
              BREAK_STRATEGY_SIMPLE,
              BREAK_STRATEGY_HIGH_QUALITY,
              BREAK_STRATEGY_BALANCED
      })
      @Retention(RetentionPolicy.SOURCE)
      public @interface BreakStrategy {}
  
      /**
       * Value for break strategy indicating simple line breaking.
       *
       * The line breaker puts words to the line as much as possible and breaks line if no more words
       * can fit into the same line. Automatic hyphens are only added when a line has a single word
       * and that word is longer than line width. This is the fastest break strategy and ideal for
       * editor.
       */
      public static final int BREAK_STRATEGY_SIMPLE = 0;
  
      /**
       * Value for break strategy indicating high quality line breaking.
       *
       * With this option line breaker does whole-paragraph optimization for more readable text, and
       * also applies automatic hyphenation when required.
       */
      public static final int BREAK_STRATEGY_HIGH_QUALITY = 1;
  
      /**
       * Value for break strategy indicating balanced line breaking.
       *
       * The line breaker does whole-paragraph optimization for making all lines similar length, and
       * also applies automatic hyphenation when required. This break strategy is good for small
       * screen devices such as watch screens.
       */
      public static final int BREAK_STRATEGY_BALANCED = 2;
  
      /** @hide */
      @IntDef(prefix = { "HYPHENATION_FREQUENCY_" }, value = {
              HYPHENATION_FREQUENCY_NORMAL,
              HYPHENATION_FREQUENCY_FULL,
              HYPHENATION_FREQUENCY_NONE
      })
      @Retention(RetentionPolicy.SOURCE)
      public @interface HyphenationFrequency {}
  
      /**
       * Value for hyphenation frequency indicating no automatic hyphenation.
       *
       * Using this option disables auto hyphenation which results in better text layout performance.
       * A word may be broken without hyphens when a line has a single word and that word is longer
       * than line width. Soft hyphens are ignored and will not be used as suggestions for potential
       * line breaks.
       */
      public static final int HYPHENATION_FREQUENCY_NONE = 0;
  
      /**
       * Value for hyphenation frequency indicating a light amount of automatic hyphenation.
       *
       * This hyphenation frequency is useful for informal cases, such as short sentences or chat
       * messages.
       */
      public static final int HYPHENATION_FREQUENCY_NORMAL = 1;
  
      /**
       * Value for hyphenation frequency indicating the full amount of automatic hyphenation.
       *
       * This hyphenation frequency is useful for running text and where it's important to put the
       * maximum amount of text in a screen with limited space.
       */
      public static final int HYPHENATION_FREQUENCY_FULL = 2;
  
      /** @hide */
      @IntDef(prefix = { "JUSTIFICATION_MODE_" }, value = {
              JUSTIFICATION_MODE_NONE,
              JUSTIFICATION_MODE_INTER_WORD
      })
      @Retention(RetentionPolicy.SOURCE)
      public @interface JustificationMode {}
  
      /**
       * Value for justification mode indicating no justification.
       */
      public static final int JUSTIFICATION_MODE_NONE = 0;
  
      /**
       * Value for justification mode indicating the text is justified by stretching word spacing.
       */
      public static final int JUSTIFICATION_MODE_INTER_WORD = 1;
  
      /**
       * Helper class for creating a {@link LineBreaker}.
       */
      public static final class Builder {
          private @BreakStrategy int mBreakStrategy = BREAK_STRATEGY_SIMPLE;
          private @HyphenationFrequency int mHyphenationFrequency = HYPHENATION_FREQUENCY_NONE;
          private @JustificationMode int mJustificationMode = JUSTIFICATION_MODE_NONE;
          private @Nullable int[] mIndents = null;
  
          /**
           * Set break strategy.
           *
           * You can change the line breaking behavior by setting break strategy. The default value is
           * {@link #BREAK_STRATEGY_SIMPLE}.
           */
          public @NonNull Builder setBreakStrategy(@BreakStrategy int breakStrategy) {
              mBreakStrategy = breakStrategy;
              return this;
          }
  
          /**
           * Set hyphenation frequency.
           *
           * You can change the amount of automatic hyphenation used. The default value is
           * {@link #HYPHENATION_FREQUENCY_NONE}.
           */
          public @NonNull Builder setHyphenationFrequency(
                  @HyphenationFrequency int hyphenationFrequency) {
              mHyphenationFrequency = hyphenationFrequency;
              return this;
          }
  
          /**
           * Set whether the text is justified.
           *
           * By setting {@link #JUSTIFICATION_MODE_INTER_WORD}, the line breaker will change the
           * internal parameters for justification.
           * The default value is {@link #JUSTIFICATION_MODE_NONE}
           */
          public @NonNull Builder setJustificationMode(@JustificationMode int justificationMode) {
              mJustificationMode = justificationMode;
              return this;
          }
  
          /**
           * Set indents.
           *
           * The supplied array provides the total amount of indentation per line, in pixel. This
           * amount is the sum of both left and right indentations. For lines past the last element in
           * the array, the indentation amount of the last element is used.
           */
          public @NonNull Builder setIndents(@Nullable int[] indents) {
              mIndents = indents;
              return this;
          }
  
          /**
           * Build a new LineBreaker with given parameters.
           *
           * You can reuse the Builder instance even after calling this method.
           */
          public @NonNull LineBreaker build() {
              return new LineBreaker(mBreakStrategy, mHyphenationFrequency, mJustificationMode,
                      mIndents);
          }
      }
  
      /**
       * Line breaking constraints for single paragraph.
       */
      public static class ParagraphConstraints {
          private @FloatRange(from = 0.0f) float mWidth = 0;
          private @FloatRange(from = 0.0f) float mFirstWidth = 0;
          private @IntRange(from = 0) int mFirstWidthLineCount = 0;
          private @Nullable float[] mVariableTabStops = null;
          private @FloatRange(from = 0) float mDefaultTabStop = 0;
  
          public ParagraphConstraints() {}
  
          /**
           * Set width for this paragraph.
           *
           * @see #getWidth()
           */
          public void setWidth(@Px @FloatRange(from = 0.0f) float width) {
              mWidth = width;
          }
  
          /**
           * Set indent for this paragraph.
           *
           * @param firstWidth the line width of the starting of the paragraph
           * @param firstWidthLineCount the number of lines that applies the firstWidth
           * @see #getFirstWidth()
           * @see #getFirstWidthLineCount()
           */
          public void setIndent(@Px @FloatRange(from = 0.0f) float firstWidth,
                  @Px @IntRange(from = 0) int firstWidthLineCount) {
              mFirstWidth = firstWidth;
              mFirstWidthLineCount = firstWidthLineCount;
          }
  
          /**
           * Set tab stops for this paragraph.
           *
           * @param tabStops the array of pixels of tap stopping position
           * @param defaultTabStop pixels of the default tab stopping position
           * @see #getTabStops()
           * @see #getDefaultTabStop()
           */
          public void setTabStops(@Nullable float[] tabStops,
                  @Px @FloatRange(from = 0) float defaultTabStop) {
              mVariableTabStops = tabStops;
              mDefaultTabStop = defaultTabStop;
          }
  
          /**
           * Return the width for this paragraph in pixels.
           *
           * @see #setWidth(float)
           */
          public @Px @FloatRange(from = 0.0f) float getWidth() {
              return mWidth;
          }
  
          /**
           * Return the first line's width for this paragraph in pixel.
           *
           * @see #setIndent(float, int)
           */
          public @Px @FloatRange(from = 0.0f) float getFirstWidth() {
              return mFirstWidth;
          }
  
          /**
           * Return the number of lines to apply the first line's width.
           *
           * @see #setIndent(float, int)
           */
          public @Px @IntRange(from = 0) int getFirstWidthLineCount() {
              return mFirstWidthLineCount;
          }
  
          /**
           * Returns the array of tab stops in pixels.
           *
           * @see #setTabStops(float[], int)
           */
          public @Nullable float[] getTabStops() {
              return mVariableTabStops;
          }
  
          /**
           * Returns the default tab stops in pixels.
           *
           * @see #setTabStop(float[], int)
           */
          public @Px @FloatRange(from = 0) float getDefaultTabStop() {
              return mDefaultTabStop;
          }
      }
  
      /**
       * Holds the result of the {@link LineBreaker#computeLineBreaks line breaking algorithm}.
       * @see LineBreaker#computeLineBreaks
       */
      public static class Result {
          // Following two contstant must be synced with minikin's line breaker.
          // TODO(nona): Remove these constatns by introducing native methods.
          private static final int TAB_MASK = 0x20000000;
          private static final int HYPHEN_MASK = 0xFF;
          private static final int START_HYPHEN_MASK = 0x18;  // 0b11000
          private static final int END_HYPHEN_MASK = 0x7;  // 0b00111
          private static final int START_HYPHEN_BITS_SHIFT = 3;
  
          private static final NativeAllocationRegistry sRegistry =
                  NativeAllocationRegistry.createMalloced(
                  Result.class.getClassLoader(), nGetReleaseResultFunc());
          private final long mPtr;
  
          private Result(long ptr) {
              mPtr = ptr;
              sRegistry.registerNativeAllocation(this, mPtr);
          }
  
          /**
           * Returns the number of lines in the paragraph.
           *
           * @return number of lines
           */
          public @IntRange(from = 0) int getLineCount() {
              return nGetLineCount(mPtr);
          }
  
          /**
           * Returns character offset of the break for a given line.
           *
           * @param lineIndex an index of the line.
           * @return the break offset.
           */
          public @IntRange(from = 0) int getLineBreakOffset(@IntRange(from = 0) int lineIndex) {
              return nGetLineBreakOffset(mPtr, lineIndex);
          }
  
          /**
           * Returns width of a given line in pixels.
           *
           * @param lineIndex an index of the line.
           * @return width of the line in pixels
           */
          public @Px float getLineWidth(@IntRange(from = 0) int lineIndex) {
              return nGetLineWidth(mPtr, lineIndex);
          }
  
          /**
           * Returns font ascent of the line in pixels.
           *
           * @param lineIndex an index of the line.
           * @return an entier font ascent of the line in pixels.
           */
          public @Px float getLineAscent(@IntRange(from = 0) int lineIndex) {
              return nGetLineAscent(mPtr, lineIndex);
          }
  
          /**
           * Returns font descent of the line in pixels.
           *
           * @param lineIndex an index of the line.
           * @return an entier font descent of the line in pixels.
           */
          public @Px float getLineDescent(@IntRange(from = 0) int lineIndex) {
              return nGetLineDescent(mPtr, lineIndex);
          }
  
          /**
           * Returns true if the line has a TAB character.
           *
           * @param lineIndex an index of the line.
           * @return true if the line has a TAB character
           */
          public boolean hasLineTab(int lineIndex) {
              return (nGetLineFlag(mPtr, lineIndex) & TAB_MASK) != 0;
          }
  
          /**
           * Returns a start hyphen edit for the line.
           *
           * @param lineIndex an index of the line.
           * @return a start hyphen edit for the line.
           *
           * @see android.graphics.Paint#setStartHyphenEdit
           * @see android.graphics.Paint#getStartHyphenEdit
           */
          public int getStartLineHyphenEdit(int lineIndex) {
              return (nGetLineFlag(mPtr, lineIndex) & START_HYPHEN_MASK) >> START_HYPHEN_BITS_SHIFT;
          }
  
          /**
           * Returns an end hyphen edit for the line.
           *
           * @param lineIndex an index of the line.
           * @return an end hyphen edit for the line.
           *
           * @see android.graphics.Paint#setEndHyphenEdit
           * @see android.graphics.Paint#getEndHyphenEdit
           */
          public int getEndLineHyphenEdit(int lineIndex) {
              return nGetLineFlag(mPtr, lineIndex) & END_HYPHEN_MASK;
          }
      }
  
      private static final NativeAllocationRegistry sRegistry =
              NativeAllocationRegistry.createMalloced(
              LineBreaker.class.getClassLoader(), nGetReleaseFunc());
  
      private final long mNativePtr;
  
      /**
       * Use Builder instead.
       */
      private LineBreaker(@BreakStrategy int breakStrategy,
              @HyphenationFrequency int hyphenationFrequency, @JustificationMode int justify,
              @Nullable int[] indents) {
          mNativePtr = nInit(breakStrategy, hyphenationFrequency,
                  justify == JUSTIFICATION_MODE_INTER_WORD, indents);
          sRegistry.registerNativeAllocation(this, mNativePtr);
      }
  
      /**
       * Break paragraph into lines.
       *
       * The result is filled to out param.
       *
       * @param measuredPara a result of the text measurement
       * @param constraints for a single paragraph
       * @param lineNumber a line number of this paragraph
       */
      public @NonNull Result computeLineBreaks(
              @NonNull MeasuredText measuredPara,
              @NonNull ParagraphConstraints constraints,
              @IntRange(from = 0) int lineNumber) {
          return new Result(nComputeLineBreaks(
                  mNativePtr,
  
                  // Inputs
                  measuredPara.getChars(),
                  measuredPara.getNativePtr(),
                  measuredPara.getChars().length,
                  constraints.mFirstWidth,
                  constraints.mFirstWidthLineCount,
                  constraints.mWidth,
                  constraints.mVariableTabStops,
                  constraints.mDefaultTabStop,
                  lineNumber));
      }
  
      @FastNative
      private static native long nInit(@BreakStrategy int breakStrategy,
              @HyphenationFrequency int hyphenationFrequency, boolean isJustified,
              @Nullable int[] indents);
  
      @CriticalNative
      private static native long nGetReleaseFunc();
  
      // populates LineBreaks and returns the number of breaks found
      //
      // the arrays inside the LineBreaks objects are passed in as well
      // to reduce the number of JNI calls in the common case where the
      // arrays do not have to be resized
      // The individual character widths will be returned in charWidths. The length of
      // charWidths must be at least the length of the text.
      private static native long nComputeLineBreaks(
              /* non zero */ long nativePtr,
  
              // Inputs
              @NonNull char[] text,
              /* Non Zero */ long measuredTextPtr,
              @IntRange(from = 0) int length,
              @FloatRange(from = 0.0f) float firstWidth,
              @IntRange(from = 0) int firstWidthLineCount,
              @FloatRange(from = 0.0f) float restWidth,
              @Nullable float[] variableTabStops,
              float defaultTabStop,
              @IntRange(from = 0) int indentsOffset);
  
      // Result accessors
      @CriticalNative
      private static native int nGetLineCount(long ptr);
      @CriticalNative
      private static native int nGetLineBreakOffset(long ptr, int idx);
      @CriticalNative
      private static native float nGetLineWidth(long ptr, int idx);
      @CriticalNative
      private static native float nGetLineAscent(long ptr, int idx);
      @CriticalNative
      private static native float nGetLineDescent(long ptr, int idx);
      @CriticalNative
      private static native int nGetLineFlag(long ptr, int idx);
      @CriticalNative
      private static native long nGetReleaseResultFunc();
  }
  