13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
|
# File 'lib/canopus/display_map/overlay_map.rb', line 13
def replace(items, rope:, font: nil, font_size: 14, line_height: 20)
raise ArgumentError, "decorations must be an array" unless items.is_a?(Array)
validate_size(font_size, "font size")
validate_size(line_height, "line height")
input = [items, font&.object_id, font_size, line_height]
return [] if @input == input
inlines, blocks = Hash.new { |hash, key| hash[key] = [] }, Hash.new { |hash, key| hash[key] = [] }
items.each do |item|
raise TypeError, "overlays must be decoration items" unless item.is_a?(Canopus::Decoration::Item)
next unless %i[inline block].include?(item.kind)
if item.kind == :inline
offset = item.range&.begin
raise ArgumentError, "inline decoration requires a range" unless offset.is_a?(Integer)
raise RangeError, "inline decoration is outside the buffer" unless offset.between?(0, rope.bytesize)
row = rope.point_at(offset).row
local = offset - rope.line_start(row)
unless Zaniah::Unicode.grapheme_boundary?(rope.line(row), local)
raise ArgumentError, "inline decoration splits a grapheme cluster"
end
style = item.style.is_a?(Hash) ? item.style : {}
align = style.fetch(:align, :after)
raise ArgumentError, "inline alignment must be before or after" unless %i[before after].include?(align)
text = item.content.is_a?(String) ? item.content : item.content.respond_to?(:text) ? item.content.text.to_s : item.content.to_s
text = text.encode(Encoding::UTF_8)
padding_left = numeric_style(style, :padding_left)
padding_right = numeric_style(style, :padding_right)
validate_size(padding_left, "inline left padding", allow_zero: true)
validate_size(padding_right, "inline right padding", allow_zero: true)
padding = padding_left + padding_right
width = numeric_style(style, :width, nil) || (font ? font.advance_width(text.codepoints, size: font_size) : Zaniah::Unicode.width(text) * font_size * 0.6)
width += padding
height = numeric_style(style, :height, line_height)
validate_size(width, "inline width")
validate_size(height, "inline height")
raise ArgumentError, "inline height must not exceed line height" if height > line_height
cells = numeric_style(style, :cells, Zaniah::Unicode.width(text))
validate_size(cells, "inline cell width", allow_zero: true)
inlines[row] << Inline.new(item, offset, width.to_f, cells.to_f, height.to_f, align)
else
row = item.row
raise ArgumentError, "block decoration requires a row" unless row.is_a?(Integer)
raise RangeError, "block decoration is outside the buffer" unless row.between?(0, rope.line_count - 1)
style = item.style.is_a?(Hash) ? item.style : {}
position = style.fetch(:position, :above)
raise ArgumentError, "block position must be above or below" unless %i[above below].include?(position)
height = numeric_style(style, :height, line_height)
validate_size(height, "block height")
blocks[row] << Block.new(item, row, height.to_f, (height.to_f / line_height).ceil, position)
end
end
inline_values = inlines.values.flatten.each_with_index
.sort_by { |value, index| [value.offset, value.item.priority, index] }.map!(&:first).freeze
inlines.each_value do |values|
ordered = values.each_with_index.sort_by { |value, index| [value.offset, value.item.priority, index] }.map!(&:first)
values.replace(ordered).freeze
end
blocks.each_value { |values| values.sort_by! { |value| value.item.priority }.freeze }
inlines, blocks = inlines.to_h.freeze, blocks.to_h.freeze
return [] if @inlines == inlines && @blocks == blocks && @line_height == line_height
affected = (@inlines.keys | @blocks.keys | inlines.keys | blocks.keys).select do |row|
@inlines.fetch(row, nil) != inlines.fetch(row, nil) ||
@blocks.fetch(row, nil) != blocks.fetch(row, nil) || @line_height != line_height
end
@inlines, @inline_values, @blocks, @line_height, @input = inlines, inline_values, blocks, line_height, input
affected.sort
end
|