Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions lib/mudbrick/content_stream/b.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
defmodule Mudbrick.ContentStream.B do
@moduledoc false

defstruct []

defimpl Mudbrick.Object do
def to_iodata(_op) do
["B"]
end
end
end
11 changes: 11 additions & 0 deletions lib/mudbrick/content_stream/f.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
defmodule Mudbrick.ContentStream.F do
@moduledoc false

defstruct []

defimpl Mudbrick.Object do
def to_iodata(_op) do
["f"]
end
end
end
18 changes: 16 additions & 2 deletions lib/mudbrick/path.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,35 @@ defmodule Mudbrick.Path do
| {:dimensions, Mudbrick.coords()}
| {:line_width, number()}
| {:colour, Mudbrick.colour()}
| {:fill, Mudbrick.colour() | nil}
| {:stroke, boolean()}

@type options :: [option()]

@typedoc """
Without `:fill` a rectangle is stroked in `:colour`, as it always was.

Given `:fill`, the interior is painted in that colour and the outline is
dropped — which is what backgrounds, bars and QR modules want. Set
`stroke: true` alongside it to get both, painted in one operation so the
two stay aligned.
"""
@type t :: %__MODULE__{
lower_left: Mudbrick.coords(),
dimensions: Mudbrick.coords(),
line_width: number(),
colour: Mudbrick.colour()
colour: Mudbrick.colour(),
fill: Mudbrick.colour() | nil,
stroke: boolean()
}

@enforce_keys [:lower_left, :dimensions]
defstruct lower_left: nil,
dimensions: nil,
line_width: 1,
colour: {0, 0, 0}
colour: {0, 0, 0},
fill: nil,
stroke: false

@doc false
@spec new(options()) :: t()
Expand Down
28 changes: 27 additions & 1 deletion lib/mudbrick/path/output.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ defmodule Mudbrick.Path.Output do
defstruct operations: []

alias Mudbrick.ContentStream.{
B,
F,
L,
M,
QPop,
Expand Down Expand Up @@ -38,7 +40,7 @@ defmodule Mudbrick.Path.Output do
|> add(%L{coords: line.to})
|> add(%S{})

%Mudbrick.Path.Rectangle{} = rect ->
%Mudbrick.Path.Rectangle{fill: nil} = rect ->
{r, g, b} = rect.colour

acc
Expand All @@ -49,6 +51,30 @@ defmodule Mudbrick.Path.Output do
dimensions: rect.dimensions
})
|> add(%S{})

%Mudbrick.Path.Rectangle{} = rect ->
# A fill colour was given. `rg` sets the non-stroking colour and
# `f` paints the interior. With `stroke: true` as well, `B` fills
# and strokes in one operation so the two stay aligned.
{fr, fg, fb} = rect.fill
{r, g, b} = rect.colour

acc
|> add(Rg.new(stroking: false, r: fr, g: fg, b: fb))
|> then(fn output ->
if rect.stroke do
output
|> add(Rg.new(stroking: true, r: r, g: g, b: b))
|> add(%W{width: rect.line_width})
else
output
end
end)
|> add(%Re{
lower_left: rect.lower_left,
dimensions: rect.dimensions
})
|> add(if rect.stroke, do: %B{}, else: %F{})
end
end
end)
Expand Down
89 changes: 89 additions & 0 deletions test/drawing_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,95 @@ defmodule Mudbrick.DrawingTest do
|> Mudbrick.TestHelper.operations()
end

test "a fill colour paints the rectangle's interior" do
import Mudbrick

assert [
"q",
"0 0.5 1 rg",
"10 10 40 20 re",
"f",
"Q"
] =
new()
|> page()
|> path(fn path ->
Path.rectangle(path,
lower_left: {10, 10},
dimensions: {40, 20},
fill: {0, 0.5, 1}
)
end)
|> Mudbrick.TestHelper.output()
|> Mudbrick.TestHelper.operations()
end

test "a fill and an outline colour together fill and stroke in one operation" do
import Mudbrick

assert [
"q",
"1 1 0 rg",
"0 0 0 RG",
"2 w",
"10 10 40 20 re",
"B",
"Q"
] =
new()
|> page()
|> path(fn path ->
Path.rectangle(path,
lower_left: {10, 10},
dimensions: {40, 20},
fill: {1, 1, 0},
colour: {0, 0, 0},
stroke: true,
line_width: 2
)
end)
|> Mudbrick.TestHelper.output()
|> Mudbrick.TestHelper.operations()
end

test "without a fill colour a rectangle is still stroked only" do
import Mudbrick

ops =
new()
|> page()
|> path(fn path ->
Path.rectangle(path, lower_left: {0, 0}, dimensions: {50, 60})
end)
|> Mudbrick.TestHelper.output()
|> Mudbrick.TestHelper.operations()

assert "S" in ops
refute "f" in ops
end

test "many filled rectangles compose, as a QR code or a bar chart would" do
import Mudbrick

ops =
new()
|> page()
|> path(fn path ->
for i <- 0..9, reduce: path do
acc ->
Path.rectangle(acc,
lower_left: {i * 4, 0},
dimensions: {3, 3},
fill: {0, 0, 0}
)
end
end)
|> Mudbrick.TestHelper.output()
|> Mudbrick.TestHelper.operations()

assert Enum.count(ops, &(&1 == "f")) == 10
end

test "can construct a rectangle" do
import Path

Expand Down