diff --git a/src/string_width.gleam b/src/string_width.gleam index 11ca416..3d646ae 100644 --- a/src/string_width.gleam +++ b/src/string_width.gleam @@ -55,8 +55,8 @@ import string_width/internal/tables // x padding layout, switch position to use padding internally // x remove do_measure // x make sure position -> padding only measures the string once -// - rewrite align (think about trimming) -// - make align trim lines? +// x rewrite align (think about trimming) +// x make align trim lines? no. // - join/columns layout (grid? table?) // - join_vertical might also be intereseting for alignment/spacing // x ansi module @@ -883,30 +883,24 @@ pub fn position_with( using options: Options, with space: String, ) -> String { - let str_size = dimensions_with(str, options) + let size = dimensions_with(str, options) let left = case alignment { Left -> 0 - Right -> int.min(str_size.columns - bounding_box.columns, 0) - Center -> int.min({ str_size.columns - bounding_box.columns } / 2, 0) + Right -> int.min(size.columns - bounding_box.columns, 0) + Center -> int.min({ size.columns - bounding_box.columns } / 2, 0) } let top = case placement { Top -> 0 - Bottom -> int.min(str_size.rows - bounding_box.rows, 0) - Middle -> int.min({ str_size.rows - bounding_box.rows } / 2, 0) + Bottom -> int.min(size.rows - bounding_box.rows, 0) + Middle -> int.min({ size.rows - bounding_box.rows } / 2, 0) } - do_scroll( - str, - str_size, - top:, - left:, - bottom: int.max(str_size.rows, bounding_box.rows), - right: int.max(str_size.columns, bounding_box.columns), - using: options, - with: space, - ) + let width = int.max(size.columns, bounding_box.columns) + let height = int.max(size.rows, bounding_box.rows) + + box(str, size, top:, left:, width:, height:, using: options, with: space) } /// Align each line horizontally, using a spacer character or string as a filler. @@ -966,7 +960,7 @@ pub fn align_with( let bottom = str_size.rows let right = int.max(str_size.columns, max_width) - use line, line_width <- box(str, str_size, 0, 0, options, bottom:, right:) + use line, line_width <- view(str, str_size, 0, 0, options, bottom:, right:) let missing = div_up(max_width - line_width, space_width) case alignment { Left -> line <> string.repeat(space, missing) @@ -1017,8 +1011,13 @@ pub fn padding( left left: Int, with space: String, ) -> String { - let using = default_options - padding_with(str, top:, right:, bottom:, left:, using:, with: space) + let options = default_options + let size = dimensions_with(str, options) + // padding is like negative scrolling - + // if we want more spaces, we need to move the viewport in the opposite direction. + let width = size.columns + left + right + let height = size.rows + top + bottom + box(str, size, options, top: -top, left: -left, width:, height:, with: space) } /// Like `padding`, but use custom options for measuring. @@ -1037,19 +1036,12 @@ pub fn padding_with( using options: Options, with space: String, ) -> String { - let str_size = dimensions_with(str, options) + let size = dimensions_with(str, options) // padding is like negative scrolling - // if we want more spaces, we need to move the viewport in the opposite direction. - do_scroll( - str, - str_size, - top: -top, - left: -left, - bottom: str_size.rows + top + bottom, - right: str_size.columns + left + right, - using: options, - with: space, - ) + let width = size.columns + left + right + let height = size.rows + top + bottom + box(str, size, options, top: -top, left: -left, width:, height:, with: space) } /// Cut out a `viewort` at the given origin `#(top, left)` from a string. This @@ -1080,22 +1072,15 @@ pub fn padding_with( /// pub fn scroll( str: String, - top row: Int, - left column: Int, + top top: Int, + left left: Int, view viewport_size: Size, with space: String, ) -> String { - let str_size = dimensions_with(str, default_options) - do_scroll( - str, - str_size, - top: row, - left: column, - bottom: viewport_size.rows, - right: viewport_size.columns, - using: default_options, - with: space, - ) + let options = default_options + let size = dimensions_with(str, options) + let Size(rows: height, columns: width) = viewport_size + box(str, size, options, top:, left:, width:, height:, with: space) } /// Like `scroll`, but provide custom options for measuring. @@ -1107,36 +1092,28 @@ pub fn scroll( /// pub fn scroll_with( str: String, - top row: Int, - left column: Int, + top top: Int, + left left: Int, view viewport_size: Size, using options: Options, with space: String, ) -> String { - let str_size = dimensions_with(str, options) - do_scroll( - str, - str_size, - top: row, - left: column, - bottom: viewport_size.rows, - right: viewport_size.columns, - using: options, - with: space, - ) + let size = dimensions_with(str, options) + let Size(rows: height, columns: width) = viewport_size + box(str, size, top:, left:, width:, height:, using: options, with: space) } type ViewportState { ViewportState(buf: String, line: String, row: Int, col: Int) } -fn do_scroll( +fn box( str: String, str_size: Size, top top: Int, left left: Int, - bottom bottom: Int, - right right: Int, + height height: Int, + width width: Int, using options: Options, with space: String, ) { @@ -1146,14 +1123,17 @@ fn do_scroll( let missing_left = div_up(int.max(0, initial_col), space_width) let padding_left = string.repeat(space, missing_left) - use line, line_width <- box(str, str_size, top, left, bottom, right, options) - let missing_right = div_up(right - int.max(0, line_width), space_width) + let bottom = top + height + let right = left + width + + use line, line_width <- view(str, str_size, top, left, bottom, right, options) + let missing_right = div_up(width - missing_left - line_width, space_width) let padding_right = string.repeat(space, missing_right) padding_left <> line <> padding_right } -fn box( +fn view( str: String, str_size: Size, top top: Int, @@ -1164,15 +1144,12 @@ fn box( align align: fn(String, Int) -> String, ) { let Size(rows: str_rows, columns: _) = str_size - // internally, we want to hit row/col 0 when we should start printing. - let initial_row = -1 * top - let initial_col = -1 * left - + let line_width_offset = int.max(0, left) let push_line = fn(state: ViewportState) -> ViewportState { - let buf = case state.row < 0 || state.row >= bottom { + let buf = case state.row < top || state.row >= bottom { True -> state.buf False -> { - let line = align(state.line, state.col) + let line = align(state.line, state.col - line_width_offset) case state.buf { "" -> line _ -> state.buf <> "\n" <> line @@ -1180,27 +1157,24 @@ fn box( } } - ViewportState(buf:, line: "", row: state.row + 1, col: initial_col) + ViewportState(buf:, line: "", row: state.row + 1, col: 0) } - let state = - ViewportState(buf: "", line: "", row: initial_row, col: initial_col) + let state = ViewportState(buf: "", line: "", row: 0, col: 0) let state = { use state, piece, width <- fold_raw(str, options, state) let width = case piece { - "\t" -> tab(options, state.col - initial_col + options.tab_offset) + "\t" -> tab(options, state.col + options.tab_offset) _ -> width } - // io.debug(#(state, piece, width)) - case piece { "\n" -> push_line(state) _ -> case - state.row < 0 + state.row < top || state.row >= bottom - || state.col < 0 + || state.col < left || state.col >= right { True -> @@ -1233,19 +1207,18 @@ fn box( } // do not push a line with padding if the last line is empty - let str = case state.col > initial_col { + let str = case state.col > 0 { True -> push_line(state).buf False -> state.buf <> state.line } // vertical padding - let padding_line = align("", initial_col) - let padding_top = string.repeat(padding_line <> "\n", initial_row) - let missing_bottom = top + bottom - str_rows - let padding_bottom = string.repeat("\n" <> padding_line, missing_bottom) + let padding_line = align("", 0) + let padding_top = string.repeat(padding_line <> "\n", -top) + let padding_bottom = string.repeat("\n" <> padding_line, bottom - str_rows) // no string at all -> padding only, but no double newline, please - case state.row > initial_row || state.col > initial_col { + case state.row > 0 || state.col > 0 { True -> padding_top <> str <> padding_bottom False -> padding_top <> str <> string.drop_left(padding_bottom, 1) }