1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.example.text.styling.roundedbg 17 18 import android.graphics.Canvas 19 import android.graphics.drawable.Drawable 20 import android.text.Layout 21 import kotlin.math.max 22 import kotlin.math.min 23 24 /** 25 * Base class for single and multi line rounded background renderers. 26 * 27 * @param horizontalPadding the padding to be applied to left & right of the background 28 * @param verticalPadding the padding to be applied to top & bottom of the background 29 */ 30 internal abstract class TextRoundedBgRenderer( 31 val horizontalPadding: Int, 32 val verticalPadding: Int 33 ) { 34 35 /** 36 * Draw the background that starts at the {@code startOffset} and ends at {@code endOffset}. 37 * 38 * @param canvas Canvas to draw onto 39 * @param layout Layout that contains the text 40 * @param startLine the start line for the background 41 * @param endLine the end line for the background 42 * @param startOffset the character offset that the background should start at 43 * @param endOffset the character offset that the background should end at 44 */ drawnull45 abstract fun draw( 46 canvas: Canvas, 47 layout: Layout, 48 startLine: Int, 49 endLine: Int, 50 startOffset: Int, 51 endOffset: Int 52 ) 53 54 /** 55 * Get the top offset of the line and add padding into account so that there is a gap between 56 * top of the background and top of the text. 57 * 58 * @param layout Layout object that contains the text 59 * @param line line number 60 */ 61 protected fun getLineTop(layout: Layout, line: Int): Int { 62 return layout.getLineTopWithoutPadding(line) - verticalPadding 63 } 64 65 /** 66 * Get the bottom offset of the line and add padding into account so that there is a gap between 67 * bottom of the background and bottom of the text. 68 * 69 * @param layout Layout object that contains the text 70 * @param line line number 71 */ getLineBottomnull72 protected fun getLineBottom(layout: Layout, line: Int): Int { 73 return layout.getLineBottomWithoutPadding(line) + verticalPadding 74 } 75 } 76 77 /** 78 * Draws the background for text that starts and ends on the same line. 79 * 80 * @param horizontalPadding the padding to be applied to left & right of the background 81 * @param verticalPadding the padding to be applied to top & bottom of the background 82 * @param drawable the drawable used to draw the background 83 */ 84 internal class SingleLineRenderer( 85 horizontalPadding: Int, 86 verticalPadding: Int, 87 val drawable: Drawable 88 ) : TextRoundedBgRenderer(horizontalPadding, verticalPadding) { 89 drawnull90 override fun draw( 91 canvas: Canvas, 92 layout: Layout, 93 startLine: Int, 94 endLine: Int, 95 startOffset: Int, 96 endOffset: Int 97 ) { 98 val lineTop = getLineTop(layout, startLine) 99 val lineBottom = getLineBottom(layout, startLine) 100 // get min of start/end for left, and max of start/end for right since we don't 101 // the language direction 102 val left = min(startOffset, endOffset) 103 val right = max(startOffset, endOffset) 104 drawable.setBounds(left, lineTop, right, lineBottom) 105 drawable.draw(canvas) 106 } 107 } 108 109 /** 110 * Draws the background for text that starts and ends on different lines. 111 * 112 * @param horizontalPadding the padding to be applied to left & right of the background 113 * @param verticalPadding the padding to be applied to top & bottom of the background 114 * @param drawableLeft the drawable used to draw left edge of the background 115 * @param drawableMid the drawable used to draw for whole line 116 * @param drawableRight the drawable used to draw right edge of the background 117 */ 118 internal class MultiLineRenderer( 119 horizontalPadding: Int, 120 verticalPadding: Int, 121 val drawableLeft: Drawable, 122 val drawableMid: Drawable, 123 val drawableRight: Drawable 124 ) : TextRoundedBgRenderer(horizontalPadding, verticalPadding) { 125 drawnull126 override fun draw( 127 canvas: Canvas, 128 layout: Layout, 129 startLine: Int, 130 endLine: Int, 131 startOffset: Int, 132 endOffset: Int 133 ) { 134 // draw the first line 135 val paragDir = layout.getParagraphDirection(startLine) 136 val lineEndOffset = if (paragDir == Layout.DIR_RIGHT_TO_LEFT) { 137 layout.getLineLeft(startLine) - horizontalPadding 138 } else { 139 layout.getLineRight(startLine) + horizontalPadding 140 }.toInt() 141 142 var lineBottom = getLineBottom(layout, startLine) 143 var lineTop = getLineTop(layout, startLine) 144 drawStart(canvas, startOffset, lineTop, lineEndOffset, lineBottom) 145 146 // for the lines in the middle draw the mid drawable 147 for (line in startLine + 1 until endLine) { 148 lineTop = getLineTop(layout, line) 149 lineBottom = getLineBottom(layout, line) 150 drawableMid.setBounds( 151 (layout.getLineLeft(line).toInt() - horizontalPadding), 152 lineTop, 153 (layout.getLineRight(line).toInt() + horizontalPadding), 154 lineBottom 155 ) 156 drawableMid.draw(canvas) 157 } 158 159 val lineStartOffset = if (paragDir == Layout.DIR_RIGHT_TO_LEFT) { 160 layout.getLineRight(startLine) + horizontalPadding 161 } else { 162 layout.getLineLeft(startLine) - horizontalPadding 163 }.toInt() 164 165 // draw the last line 166 lineBottom = getLineBottom(layout, endLine) 167 lineTop = getLineTop(layout, endLine) 168 169 drawEnd(canvas, lineStartOffset, lineTop, endOffset, lineBottom) 170 } 171 172 /** 173 * Draw the first line of a multiline annotation. Handles LTR/RTL. 174 * 175 * @param canvas Canvas to draw onto 176 * @param start start coordinate for the background 177 * @param top top coordinate for the background 178 * @param end end coordinate for the background 179 * @param bottom bottom coordinate for the background 180 */ drawStartnull181 private fun drawStart(canvas: Canvas, start: Int, top: Int, end: Int, bottom: Int) { 182 if (start > end) { 183 drawableRight.setBounds(end, top, start, bottom) 184 drawableRight.draw(canvas) 185 } else { 186 drawableLeft.setBounds(start, top, end, bottom) 187 drawableLeft.draw(canvas) 188 } 189 } 190 191 /** 192 * Draw the last line of a multiline annotation. Handles LTR/RTL. 193 * 194 * @param canvas Canvas to draw onto 195 * @param start start coordinate for the background 196 * @param top top position for the background 197 * @param end end coordinate for the background 198 * @param bottom bottom coordinate for the background 199 */ drawEndnull200 private fun drawEnd(canvas: Canvas, start: Int, top: Int, end: Int, bottom: Int) { 201 if (start > end) { 202 drawableLeft.setBounds(end, top, start, bottom) 203 drawableLeft.draw(canvas) 204 } else { 205 drawableRight.setBounds(start, top, end, bottom) 206 drawableRight.draw(canvas) 207 } 208 } 209 }