Relative text positions based on previous text height
bbugh opened this issue · 2 comments
Hi 👋
I have a use case where I need to write out a table-ish data list. Unfortunately, a few of the entries can be taller than one line, so I'm using relative positioning to output each value, but I can't find any way to increment the y
based on height.
I'm currently double-printing to get the height
from the return value of text
, but I can't find any way to access the row index so I can use the height
%i[col1, col2, col3].each_with_index do |column, index|
# Pre-print in order to calculate the height of the text
info = text str: data[column], layout: 'label', ellipsize: false
# This is the problem - seems like no way to get height⁉️ value for the current "row"
# I can get it from the "colum
height = info[ROW_INDEX⁉️][:height]
y_pos = y_pos + (LINE_PADDING * 2) + height⁉️
# draw alternating backgrounds
if index.even?
rect x: '4.25in', y: y_pos - LINE_PADDING, width: '3.75in', height: height⁉️ + (LINE_PADDING * 2), fill_color: '#F5F3F4', stroke_width: 0
end
# draw the real text
text layout: "stat_text", y: y_pos, ellipsize: false, str: data[column], height: ⁉️
end
I've dug through the issues and documentation for quite a while and can't find any way to do this. I see #201 and #295, but really I just need a way to set the height based on the current "row". Does this exist?
The info
here refers to the index of the card - card 0, card 1, etc. So that might be different based on how the text on each card was rendered. So, your y_pos should probably be handled as an array. To get all the heights for all the cards, you can do something like this:
# renamed info to extents
extents = text str: data[column], layout: 'label', ellipsize: false
heights = extents.map { |e| e[:height] }
# you can modify the map here to modify each height as necessary to get your y_pos array
I think that will get you where you need... but, that being said, here are some thoughts:
- If you need to show things in a table, resizing rows based on previous rows might be confusing to players - if I'm holding two cards and want to compare them, I would want, say "HP" to be in the same position each time - even if that means there are blanks spots in the table. Without knowing what you're going for, I wonder if that's a good idea?
- If you want things to flow together in more of a paragraph, then concatenating the text can work well. You can do this in several places, and I tend to do it in Excel (although injecting newlines can be tricky there). To do it in Ruby, it would be something like
data[:new_col] = data[col1].zip(data[col2], data[col3]) { |c1, c2, c3| c1 + c2 + c3}
. You can zip as many columns as you need, and then inject newlines. - You could also flow things in a paragraph and if you need to space things arbitrarily, you could embed an empty image in the text. Squib doesn't do tab stops - although Pango does so we could technically add tab stops if we feel like we need it
Thanks!
I ended up pre-measuring all of the possible text, getting the heights, and then transposing the big array into the appropriate format.
COLUMNS.each do |column|
measured_text = text str: data[column], layout: "text_value", ellipsize: false
column_heights[column] = measured_text.map { |x| x[:height] }
end
card_column_heights = column_heights.values.transpose
positions = card_column_heights.map do |card|
next_y_pos = 0
positions = card.map.with_index do |cur_height, i|
y_pos = i == 0 ? START_Y_POS : next_y_pos
height = cur_height + (LINE_PADDING * 2)
next_y_pos = y_pos + cur_height + (LINE_PADDING * 2)
{ y_pos: y_pos, height: height }
end
end
column_y_positions = positions.transpose.map { |list| list.map { |x| x[:y_pos] } }
column_heights = positions.transpose.map { |list| list.map { |x| x[:height] } }
COLUMNS.each_with_index do |column, column_index|
if column_index.even?
rect x: '4.25in', y: column_y_positions[column_index], width: '3.75in', height: column_heights[column_index], fill_color: '#F5F3F4', stroke_width: 0
end
text str: column.titleize, layout: "text_label", y: column_y_positions[column_index].map { |x| x + LINE_PADDING }
text str: data[column], layout: "text_value", y: column_y_positions[column_index].map { |x| x + LINE_PADDING }, ellipsize: false
end
It's a little messy and can possibly be simplified, but it works fast and exactly as desired.
If you need to show things in a table, resizing rows based on previous rows might be confusing to players
I agree, but we're making flashcards for memorization!