test/hexapdf/layout/test_text_layouter.rb in hexapdf-0.7.0 vs test/hexapdf/layout/test_text_layouter.rb in hexapdf-0.8.0
- old
+ new
@@ -361,178 +361,253 @@
@doc = HexaPDF::Document.new
@font = @doc.fonts.add("Times")
@style = HexaPDF::Layout::Style.new(font: @font)
end
- it "creates an instance from text and options" do
- layouter = HexaPDF::Layout::TextLayouter.create("T", font: @font, width: 100, height: 100)
- assert_equal(1, layouter.items.length)
- assert_equal(@font.decode_utf8("T"), layouter.items[0].item.items)
- end
-
describe "initialize" do
it "can use a Style object" do
style = HexaPDF::Layout::Style.new(font: @font, font_size: 20)
- layouter = HexaPDF::Layout::TextLayouter.new(items: [], width: 10, style: style)
+ layouter = HexaPDF::Layout::TextLayouter.new(style)
assert_equal(20, layouter.style.font_size)
end
it "can use a style options" do
- layouter = HexaPDF::Layout::TextLayouter.new(items: [], width: 10, style:
- {font: @font, font_size: 20})
+ layouter = HexaPDF::Layout::TextLayouter.new(font: @font, font_size: 20)
assert_equal(20, layouter.style.font_size)
end
end
- it "doesn't run the text segmentation algorithm on already segmented items" do
- item = HexaPDF::Layout::InlineBox.create(width: 20) {}
- layouter = HexaPDF::Layout::TextLayouter.new(items: [item], width: 100, height: 100)
- items = layouter.items
- assert_equal(1, items.length)
- assert_box(items[0], item)
+ describe "fit" do
+ before do
+ @layouter = HexaPDF::Layout::TextLayouter.new(@style)
+ end
- layouter.items = items
- assert_same(items, layouter.items)
- end
+ it "does nothing if there are no items" do
+ result = @layouter.fit([], 100, 100)
+ assert_equal(:success, result.status)
+ assert_equal(0, result.height)
+ end
- describe "fit" do
it "handles text indentation" do
items = boxes([20, 20], [20, 20], [20, 20]) +
[HexaPDF::Layout::TextLayouter::Penalty::MandatoryParagraphBreak] +
boxes([40, 20]) + [glue(20)] +
boxes(*([[20, 20]] * 4)) + [HexaPDF::Layout::TextLayouter::Penalty::MandatoryLineBreak] +
boxes(*([[20, 20]] * 4))
@style.text_indent = 20
[60, proc { 60 }].each do |width|
- layouter = HexaPDF::Layout::TextLayouter.new(items: items, width: width, style: @style)
- rest, reason = layouter.fit
- assert_equal([40, 20, 40, 60, 20, 60, 20], layouter.lines.map(&:width))
- assert_equal([20, 0, 20, 0, 0, 0, 0], layouter.lines.map(&:x_offset))
- assert(rest.empty?)
- assert_equal(:success, reason)
+ result = @layouter.fit(items, width, 200)
+ assert_equal([40, 20, 40, 60, 20, 60, 20], result.lines.map(&:width))
+ assert_equal([20, 0, 20, 0, 0, 0, 0], result.lines.map(&:x_offset))
+ assert(result.remaining_items.empty?)
+ assert_equal(:success, result.status)
end
end
- it "fits using unlimited height" do
- layouter = HexaPDF::Layout::TextLayouter.new(items: boxes(*([[20, 20]] * 100)), width: 20,
- style: @style)
- rest, reason = layouter.fit
- assert(rest.empty?)
- assert_equal(:success, reason)
- assert_equal(20 * 100, layouter.actual_height)
- end
-
it "fits using a limited height" do
- layouter = HexaPDF::Layout::TextLayouter.new(items: boxes(*([[20, 20]] * 100)), width: 20,
- height: 100, style: @style)
- rest, reason = layouter.fit
- assert_equal(95, rest.count)
- assert_equal(:height, reason)
- assert_equal(100, layouter.actual_height)
+ result = @layouter.fit(boxes(*([[20, 20]] * 100)), 20, 100)
+ assert_equal(95, result.remaining_items.count)
+ assert_equal(:height, result.status)
+ assert_equal(100, result.height)
end
it "takes line spacing into account when calculating the height" do
- layouter = HexaPDF::Layout::TextLayouter.new(items: boxes(*([[20, 20]] * 5)), width: 20,
- style: @style)
- layouter.style.line_spacing = :double
- rest, reason = layouter.fit
- assert(rest.empty?)
- assert_equal(:success, reason)
- assert_equal(20 * (5 + 4), layouter.actual_height)
+ @style.line_spacing = :double
+ result = @layouter.fit(boxes(*([[20, 20]] * 5)), 20, 200)
+ assert(result.remaining_items.empty?)
+ assert_equal(:success, result.status)
+ assert_equal(20 * (5 + 4), result.height)
end
it "handles empty lines" do
items = boxes([20, 20]) + [penalty(-5000)] + boxes([30, 20]) + [penalty(-5000)] * 2 +
boxes([20, 20]) + [penalty(-5000)] * 2
- layouter = HexaPDF::Layout::TextLayouter.new(items: items, width: 30, style: @style)
- rest, reason = layouter.fit
- assert(rest.empty?)
- assert_equal(:success, reason)
- assert_equal(5, layouter.lines.count)
- assert_equal(20 + 20 + 9 + 20 + 9, layouter.actual_height)
+ result = @layouter.fit(items, 30, 100)
+ assert(result.remaining_items.empty?)
+ assert_equal(:success, result.status)
+ assert_equal(5, result.lines.count)
+ assert_equal(20 + 20 + 9 + 20 + 9, result.height)
end
- describe "fixed width" do
- it "stops if an item is wider than the available width, with unlimited height" do
- layouter = HexaPDF::Layout::TextLayouter.new(items: boxes([20, 20], [50, 20]), width: 30,
- style: @style)
- rest, reason = layouter.fit
- assert_equal(1, rest.count)
- assert_equal(:box, reason)
- assert_equal(20, layouter.actual_height)
+ it "handles line breaks in combination with multiple parts per line" do
+ items = boxes([20, 20]) + [penalty(-5000)] +
+ boxes([20, 20], [20, 20], [20, 20]) + [penalty(-5000)] +
+ [penalty(-5000)] +
+ boxes([20, 20])
+ result = @layouter.fit(items, [0, 40, 0, 40, 0, 40], 100)
+ assert_equal(:success, result.status)
+ assert_equal([20, 40, 20, 0, 20], result.lines.map(&:width))
+ assert_equal([20, 20, 0, 6.83, 22.17], result.lines.map(&:y_offset))
+ end
+
+ describe "fixed width and too wide item" do
+ it "single part per line" do
+ result = @layouter.fit(boxes([20, 20], [50, 20]), 30, 100)
+ assert_equal(1, result.remaining_items.count)
+ assert_equal(:box_too_wide, result.status)
+ assert_equal(20, result.height)
end
- it "stops if a box item is wider than the available width, with limited height" do
- layouter = HexaPDF::Layout::TextLayouter.new(items: boxes([20, 20], [50, 20]), width: 30,
- height: 100, style: @style)
- rest, reason = layouter.fit
- assert_equal(1, rest.count)
- assert_equal(:box, reason)
- assert_equal(20, layouter.actual_height)
+ it "multiple parts per line, one fits" do
+ result = @layouter.fit(boxes([20, 20], [40, 20], [40, 20], [10, 20]),
+ [0, 30, 0, 50, 0, 30, 0, 30], 100)
+ assert_equal(0, result.remaining_items.count)
+ assert_equal([20, 40, 0, 0, 0, 50], result.lines.map(&:width))
+ assert_equal([0, 30, 80, 110, 0, 30], result.lines.map(&:x_offset))
+ assert_equal([20, 0, 0, 0, 20, 0], result.lines.map(&:y_offset))
+ assert_equal(:success, result.status)
+ assert_equal(40, result.height)
end
+
+ it "multiple parts per line, none fits" do
+ result = @layouter.fit(boxes([20, 20], [50, 20]), [0, 30, 0, 30, 0, 30], 100)
+ assert_equal(1, result.remaining_items.count)
+ assert_equal(:box_too_wide, result.status)
+ assert_equal(20, result.height)
+ end
end
- describe "variable width with limited height" do
- it "searches for a vertical offset if the first item is wider than the available width" do
+ describe "variable width" do
+ it "single part per line, searches for vertical offset if the first item is too wide" do
width_block = lambda do |height, _|
case height
when 0..20 then 10
else 40
end
end
- layouter = HexaPDF::Layout::TextLayouter.new(items: boxes([20, 18]), width: width_block,
- height: 100, style: @style)
- rest, reason = layouter.fit
- assert(rest.empty?)
- assert_equal(:success, reason)
- assert_equal(1, layouter.lines.count)
- assert_equal(24, layouter.lines[0].y_offset)
- assert_equal(42, layouter.actual_height)
+ result = @layouter.fit(boxes([20, 18]), width_block, 100)
+ assert(result.remaining_items.empty?)
+ assert_equal(:success, result.status)
+ assert_equal(1, result.lines.count)
+ assert_equal(42, result.lines[0].y_offset)
+ assert_equal(42, result.height)
end
- it "searches for a vertical offset if an item is wider than the available width" do
+ it "single part per line, searches for vertical offset if an item is too wide" do
width_block = lambda do |height, line_height|
if (40..60).cover?(height) || (40..60).cover?(height + line_height)
10
else
40
end
end
- layouter = HexaPDF::Layout::TextLayouter.new(items: boxes(*([[20, 18]] * 7)),
- width: width_block, height: 100, style: @style)
- rest, reason = layouter.fit
- assert_equal(1, rest.count)
- assert_equal(:height, reason)
- assert_equal(3, layouter.lines.count)
- assert_equal(0, layouter.lines[0].y_offset)
- assert_equal(18, layouter.lines[1].y_offset)
- assert_equal(48, layouter.lines[2].y_offset)
- assert_equal(84, layouter.actual_height)
+ result = @layouter.fit(boxes(*([[20, 18]] * 7)), width_block, 100)
+ assert_equal(1, result.remaining_items.count)
+ assert_equal(:height, result.status)
+ assert_equal(3, result.lines.count)
+ assert_equal(18, result.lines[0].y_offset)
+ assert_equal(18, result.lines[1].y_offset)
+ assert_equal(48, result.lines[2].y_offset)
+ assert_equal(84, result.height)
end
+
+ it "multiple parts per line, reset line because of line height change during first part" do
+ width_block = lambda do |height, line_height|
+ if height == 0 && line_height <= 10
+ [0, 40, 0, 40]
+ elsif height == 0 && line_height > 10
+ [0, 30, 0, 40]
+ else
+ [0, 60]
+ end
+ end
+ result = @layouter.fit(boxes([20, 10], [20, 10], [20, 18], [20, 10]), width_block, 100)
+ assert_equal(:success, result.status)
+ assert_equal(0, result.remaining_items.count)
+ assert_equal(3, result.lines.count)
+ assert_equal([20, 40, 20], result.lines.map(&:width))
+ assert_equal([18, 0, 10], result.lines.map(&:y_offset))
+ assert_equal(28, result.height)
+ end
+
+ it "multiple parts per line, reset line because of line height change after first part" do
+ width_block = lambda do |height, line_height|
+ if height == 0 && line_height <= 10
+ [0, 40, 0, 40]
+ elsif height == 0 && line_height > 10
+ [0, 30, 0, 40]
+ else
+ [0, 60]
+ end
+ end
+ result = @layouter.fit(boxes([20, 10], [15, 10], [10, 10], [12, 18], [20, 10]),
+ width_block, 100)
+ assert_equal(:success, result.status)
+ assert_equal(0, result.remaining_items.count)
+ assert_equal(3, result.lines.count)
+ assert_equal([20, 37, 20], result.lines.map(&:width))
+ assert_equal([18, 0, 10], result.lines.map(&:y_offset))
+ assert_equal(28, result.height)
+ end
end
it "breaks a text fragment into parts if it is wider than the available width" do
- [100, nil].each do |height|
- str = " Thisisaverylongstring"
- frag = HexaPDF::Layout::TextFragment.create(str, font: @font)
- layouter = HexaPDF::Layout::TextLayouter.new(items: [frag], width: 20, height: height,
- style: @style)
- rest, reason = layouter.fit
- assert(rest.empty?)
- assert_equal(:success, reason)
- assert_equal(str.strip.length, layouter.lines.sum {|l| l.items.sum {|i| i.items.count } })
- assert_equal(45, layouter.actual_height)
+ str = " Thisisaverylongstring"
+ frag = HexaPDF::Layout::TextFragment.create(str, font: @font)
+ result = @layouter.fit([frag], 20, 100)
+ assert(result.remaining_items.empty?)
+ assert_equal(:success, result.status)
+ assert_equal(str.strip.length, result.lines.sum {|l| l.items.sum {|i| i.items.count } })
+ assert_equal(45, result.height)
- layouter = HexaPDF::Layout::TextLayouter.new(items: [frag], width: 1, height: height,
- style: @style)
- rest, reason = layouter.fit
- assert_equal(str.strip.length, rest.count)
- assert_equal(:box, reason)
+ result = @layouter.fit([frag], 1, 100)
+ assert_equal(str.strip.length, result.remaining_items.count)
+ assert_equal(:box_too_wide, result.status)
+ end
+
+ describe "horizontal alignment" do
+ before do
+ @items = boxes(*[[20, 20]] * 4)
end
+
+ it "aligns the contents to the left" do
+ @style.align = :left
+ result = @layouter.fit(@items, 100, 100)
+ assert_equal(0, result.lines[0].x_offset)
+ end
+
+ it "aligns the contents to the center" do
+ @style.align = :center
+ result = @layouter.fit(@items, 100, 100)
+ assert_equal(10, result.lines[0].x_offset)
+ end
+
+ it "aligns the contents to the right" do
+ @style.align = :right
+ result = @layouter.fit(@items, 100, 100)
+ assert_equal(20, result.lines[0].x_offset)
+ end
end
+ describe "vertical alignment" do
+ before do
+ @items = boxes(*[[20, 20]] * 4)
+ end
+
+ it "aligns the contents to the top" do
+ @style.valign = :top
+ result = @layouter.fit(@items, 40, 100)
+ assert_equal(result.lines[0].y_max, result.lines[0].y_offset)
+ assert_equal(40, result.height)
+ end
+
+ it "aligns the contents to the center" do
+ @style.valign = :center
+ result = @layouter.fit(@items, 40, 100)
+ assert_equal((100 - 40) / 2 + 20, result.lines[0].y_offset)
+ assert_equal(70, result.height)
+ end
+
+ it "aligns the contents to the bottom" do
+ @style.valign = :bottom
+ result = @layouter.fit(@items, 40, 100)
+ assert_equal(100 - 20 * 2 + 20, result.lines[0].y_offset)
+ assert_equal(100, result.height)
+ end
+ end
+
it "post-processes lines for justification if needed" do
frag10 = HexaPDF::Layout::TextFragment.create(" ", font: @font)
frag10.items.freeze
frag10b = HexaPDF::Layout::TextLayouter::Box.new(frag10)
frag20 = HexaPDF::Layout::TextFragment.create(" ", font: @font, font_size: 20)
@@ -541,38 +616,24 @@
# Width of spaces: 2.5 * 2 + 5 = 10 (from AFM file, adjusted for font size)
# Line width: 20 * 4 + width_of_spaces = 90
# Missing width: 100 - 90 = 10
# -> Each space must be doubled!
- layouter = HexaPDF::Layout::TextLayouter.new(items: items, width: 100)
- layouter.style.align = :justify
- rest, reason = layouter.fit
- assert(rest.empty?)
- assert_equal(:success, reason)
- assert_equal(9, layouter.lines[0].items.count)
- assert_in_delta(100, layouter.lines[0].width)
- assert_equal(-250, layouter.lines[0].items[1].items[0])
- assert_equal(-250, layouter.lines[0].items[4].items[0])
- assert_equal(-250, layouter.lines[0].items[6].items[0])
- assert_equal(30, layouter.lines[1].width)
+ @style.align = :justify
+ result = @layouter.fit(items, 100, 100)
+ assert(result.remaining_items.empty?)
+ assert_equal(:success, result.status)
+ assert_equal(9, result.lines[0].items.count)
+ assert_in_delta(100, result.lines[0].width)
+ assert_equal(-250, result.lines[0].items[1].items[0])
+ assert_equal(-250, result.lines[0].items[4].items[0])
+ assert_equal(-250, result.lines[0].items[6].items[0])
+ assert_equal(30, result.lines[1].width)
end
-
- it "applies the optional horizontal offsets if set" do
- x_offsets = lambda {|height, line_height| height + line_height }
- layouter = HexaPDF::Layout::TextLayouter.new(items: boxes(*([[20, 10]] * 7)), width: 60,
- x_offsets: x_offsets, height: 100, style: @style)
- rest, reason = layouter.fit
- assert(rest.empty?)
- assert_equal(:success, reason)
- assert_equal(30, layouter.actual_height)
- assert_equal(10, layouter.lines[0].x_offset)
- assert_equal(20, layouter.lines[1].x_offset)
- assert_equal(30, layouter.lines[2].x_offset)
- end
end
- describe "draw" do
+ describe "Result#draw" do
def assert_positions(content, positions)
processor = TestHelper::OperatorRecorder.new
HexaPDF::Content::Parser.new.parse(content, processor)
result = processor.recorded_ops
leading = (result.select {|name, _| name == :set_leading } || [0]).map(&:last).flatten.first
@@ -594,136 +655,56 @@
before do
@frag = HexaPDF::Layout::TextFragment.create("This is some more text.\n" \
"This is some more text.", font: @font)
@width = HexaPDF::Layout::TextFragment.create("This is some ", font: @font).width
- @layouter = HexaPDF::Layout::TextLayouter.new(items: [@frag], width: @width)
+ @layouter = HexaPDF::Layout::TextLayouter.new
@canvas = @doc.pages.add.canvas
@line1w = HexaPDF::Layout::TextFragment.create("This is some", font: @font).width
@line2w = HexaPDF::Layout::TextFragment.create("more text.", font: @font).width
end
- it "returns the result of #fit if #fit needs to be run" do
- rest, reason = @layouter.draw(@canvas, 0, 0)
- assert(rest.empty?)
- assert_equal(:success, reason)
-
- @layouter.items = [HexaPDF::Layout::InlineBox.create(width: @width + 10) {}]
- rest, reason = @layouter.draw(@canvas, 0, 0)
- assert_equal(1, rest.size)
- assert_equal(:box, reason)
-
- assert_nil(@layouter.draw(@canvas, 0, 0))
- end
-
- it "can horizontally align the contents to the left" do
+ it "respects the x- and y-offsets" do
top = 100
- @layouter.style.align = :left
- @layouter.draw(@canvas, 5, top)
- assert_positions(@canvas.contents,
- [[5, top - @frag.y_max],
- [5, top - @frag.y_max - @frag.height],
- [5, top - @frag.y_max - @frag.height * 2],
- [5, top - @frag.y_max - @frag.height * 3]])
- end
-
- it "can horizontally align the contents to the center" do
- top = 100
+ @layouter.style.valign = :center
@layouter.style.align = :center
- @layouter.draw(@canvas, 5, top)
- assert_positions(@canvas.contents,
- [[5 + (@width - @line1w) / 2, top - @frag.y_max],
- [5 + (@width - @line2w) / 2, top - @frag.y_max - @frag.height],
- [5 + (@width - @line1w) / 2, top - @frag.y_max - @frag.height * 2],
- [5 + (@width - @line2w) / 2, top - @frag.y_max - @frag.height * 3]])
- end
- it "can horizontally align the contents to the right" do
- top = 100
- @layouter.style.align = :right
- @layouter.draw(@canvas, 5, top)
- assert_positions(@canvas.contents,
- [[5 + @width - @line1w, top - @frag.y_max],
- [5 + @width - @line2w, top - @frag.y_max - @frag.height],
- [5 + @width - @line1w, top - @frag.y_max - @frag.height * 2],
- [5 + @width - @line2w, top - @frag.y_max - @frag.height * 3]])
- end
+ result = @layouter.fit([@frag], @width, top)
+ result.draw(@canvas, 5, top)
- it "can justify the contents" do
- top = 100
- @layouter.style.align = :justify
- @layouter.draw(@canvas, 5, top)
+ initial_baseline = top - result.lines.first.y_offset
assert_positions(@canvas.contents,
- [[5, top - @frag.y_max],
- [5, top - @frag.y_max - @frag.height],
- [5, top - @frag.y_max - @frag.height * 2],
- [5, top - @frag.y_max - @frag.height * 3]])
- assert_in_delta(@width, @layouter.lines[0].width, 0.0001)
- assert_in_delta(@width, @layouter.lines[2].width, 0.0001)
+ [[5 + (@width - @line1w) / 2, initial_baseline],
+ [5 + (@width - @line2w) / 2, initial_baseline - @frag.height],
+ [5 + (@width - @line1w) / 2, initial_baseline - @frag.height * 2],
+ [5 + (@width - @line2w) / 2, initial_baseline - @frag.height * 3]])
end
- it "doesn't justify lines ending in a mandatory break or the last line" do
- @layouter.style.align = :justify
- @layouter.draw(@canvas, 5, 100)
- assert_equal(@line2w, @layouter.lines[1].width, 0.0001)
- assert_equal(@line2w, @layouter.lines[3].width, 0.0001)
- end
-
- it "can vertically align the contents in the center" do
- top = 100
- @layouter = HexaPDF::Layout::TextLayouter.new(items: [@frag], width: @width, height: top)
- @layouter.style.valign = :center
-
- @layouter.fit
- initial_baseline = top - ((top - @layouter.actual_height) / 2) - @frag.y_max
- @layouter.draw(@canvas, 5, top)
- assert_positions(@canvas.contents,
- [[5, initial_baseline],
- [5, initial_baseline - @frag.height],
- [5, initial_baseline - @frag.height * 2],
- [5, initial_baseline - @frag.height * 3]])
- end
-
- it "can vertically align the contents to the bottom" do
- top = 100
- @layouter = HexaPDF::Layout::TextLayouter.new(items: [@frag], width: @width, height: top)
- @layouter.style.valign = :bottom
-
- @layouter.fit
- initial_baseline = @layouter.actual_height - @frag.y_max
- @layouter.draw(@canvas, 5, top)
- assert_positions(@canvas.contents,
- [[5, initial_baseline],
- [5, initial_baseline - @frag.height],
- [5, initial_baseline - @frag.height * 2],
- [5, initial_baseline - @frag.height * 3]])
- end
-
- it "raises an error if vertical alignment is :center/:bottom and an unlimited height is used" do
- @layouter = HexaPDF::Layout::TextLayouter.new(items: [@frag], width: @width)
- assert_raises(HexaPDF::Error) do
- @layouter.style.valign = :center
- @layouter.draw(@canvas, 0, 0)
- end
- assert_raises(HexaPDF::Error) do
- @layouter.style.valign = :bottom
- @layouter.draw(@canvas, 0, 0)
- end
- end
-
it "makes sure that text fragments don't pollute the graphics state for inline boxes" do
- frag = HexaPDF::Layout::TextFragment.create("Demo", font: @font)
inline_box = HexaPDF::Layout::InlineBox.create(width: 10, height: 10) {|c, _| c.text("A") }
- layouter = HexaPDF::Layout::TextLayouter.new(items: [frag, inline_box], width: 200)
- assert_raises(HexaPDF::Error) { layouter.draw(@canvas, 0, 0) }
+ result = @layouter.fit([@frag, inline_box], 200, 100)
+ assert_raises(HexaPDF::Error) { result.draw(@canvas, 0, 0) } # bc font should be reset to nil
end
+ it "doesn't do unnecessary work for consecutive text fragments with same style" do
+ @layouter.fit([@frag], 200, 100).draw(@canvas, 0, 0)
+ assert_operators(@canvas.contents, [[:save_graphics_state],
+ [:set_leading, [9.0]],
+ [:set_font_and_size, [:F1, 10]],
+ [:begin_text],
+ [:move_text, [0, -6.83]],
+ [:show_text, ["This is some more text."]],
+ [:move_text_next_line],
+ [:show_text, ["This is some more text."]],
+ [:end_text],
+ [:restore_graphics_state]])
+ end
+
it "doesn't do unnecessary work for placeholder boxes" do
box1 = HexaPDF::Layout::InlineBox.create(width: 10, height: 20)
box2 = HexaPDF::Layout::InlineBox.create(width: 30, height: 40) { @canvas.line_width(2) }
- layouter = HexaPDF::Layout::TextLayouter.new(items: [box1, box2], width: 200)
- layouter.draw(@canvas, 0, 0)
+ @layouter.fit([box1, box2], 200, 100).draw(@canvas, 0, 0)
assert_operators(@canvas.contents, [[:save_graphics_state],
[:restore_graphics_state],
[:save_graphics_state],
[:concatenate_matrix, [1, 0, 0, 1, 10, -40]],
[:set_line_width, [2]],