Skip to content

Commit

Permalink
Text block colours
Browse files Browse the repository at this point in the history
  • Loading branch information
camelpunch committed Oct 26, 2024
1 parent 5114abf commit eebbe9d
Show file tree
Hide file tree
Showing 2 changed files with 180 additions and 78 deletions.
76 changes: 57 additions & 19 deletions lib/mudbrick/text_block.ex
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ defmodule Mudbrick.TextBlock do
defstruct operations: []

alias Mudbrick.ContentStream.{BT, ET}
alias Mudbrick.ContentStream.Rg
alias Mudbrick.ContentStream.Td
alias Mudbrick.ContentStream.Tf
alias Mudbrick.ContentStream.{Tj, Apostrophe}
Expand All @@ -47,15 +48,41 @@ defmodule Mudbrick.TextBlock do
|> add(%TL{leading: leading(tb)})
|> add(%Td{tx: x, ty: y})
|> then(fn output ->
[%{parts: [first_part]} | rest] = tb.lines
[%Line{parts: first_parts} | other_lines] = tb.lines |> Enum.reverse()

output
|> Output.add(%Tj{font: font, text: first_part.text})
|> then(
&for %{parts: [part]} <- rest, reduce: &1 do
acc -> Output.add(acc, %Apostrophe{font: font, text: part.text})
end
&List.foldr(first_parts, &1, fn %Line.Part{text: text, colour: colour}, acc ->
acc
|> colour(colour)
|> add(%Tj{font: font, text: text})
end)
)
|> then(fn output ->
for %Line{parts: parts} <- other_lines, reduce: output do
acc ->
{_, acc} =
List.foldr(parts, {false, acc}, fn
%Line.Part{text: text, colour: colour}, {false, inner_acc} ->
{
true,
inner_acc
|> colour(colour)
|> add(%Apostrophe{font: font, text: text})
}

%Line.Part{text: text, colour: colour}, {true, inner_acc} ->
{
true,
inner_acc
|> colour(colour)
|> add(%Tj{font: font, text: text})
}
end)

acc
end
end)
end)
|> end_block()

Expand All @@ -75,11 +102,11 @@ defmodule Mudbrick.TextBlock do
|> add(%Tf{font: font, size: font_size})
|> add(%TL{leading: leading(tb)})
|> then(fn output ->
[%{parts: [first_part]} | rest] = tb.lines
[%{parts: [first_part]} | rest] = Enum.reverse(tb.lines)

output
|> right_offset(tb, first_part.text, 1)
|> Output.add(%Tj{font: font, text: first_part.text})
|> add(%Tj{font: font, text: first_part.text})
|> end_block()
|> then(fn output ->
{_line, output} =
Expand All @@ -90,7 +117,7 @@ defmodule Mudbrick.TextBlock do
acc
|> start_block()
|> right_offset(tb, part.text, line)
|> Output.add(%Tj{font: font, text: part.text})
|> add(%Tj{font: font, text: part.text})
|> end_block()
}
end
Expand All @@ -102,28 +129,39 @@ defmodule Mudbrick.TextBlock do
output.operations
end

def add(%__MODULE__{} = output, op) do
Map.update!(output, :operations, &[op | &1])
end

def start_block(output) do
defp start_block(output) do
add(output, %BT{})
end

def end_block(output) do
defp end_block(output) do
add(output, %ET{})
end

def right_offset(output, tb, text, line) do
defp right_offset(output, tb, text, line) do
n = line - 1
{x, y} = tb.position

Output.add(output, %Td{
add(output, %Td{
tx: x - Font.width(tb.font, tb.font_size, text),
ty: y - leading(tb) * n
})
end

defp colour(output, {r, g, b}) do
new_colour = %Rg{r: r, g: g, b: b}
latest_colour = Enum.find(output.operations, &match?(%Rg{}, &1)) || %Rg{r: 0, g: 0, b: 0}

if latest_colour != new_colour do
add(output, new_colour)
else
output
end
end

defp add(%__MODULE__{} = output, op) do
Map.update!(output, :operations, &[op | &1])
end

defp leading(tb) do
tb.font_size * 1.2
end
Expand All @@ -144,9 +182,9 @@ defmodule Mudbrick.TextBlock do
def write(tb, text, opts \\ []) do
Map.update!(tb, :lines, fn
[] ->
text
|> String.split("\n")
|> Enum.map(&Line.wrap(&1, opts))
for text <- String.split(text, "\n"), reduce: [] do
acc -> [Line.wrap(text, opts) | acc]
end

[%Line{} = previous_line | existing_lines] ->
[first_new_line_text | new_line_texts] = String.split(text, "\n")
Expand Down
182 changes: 123 additions & 59 deletions test/mudbrick/text_block_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,21 @@ defmodule Mudbrick.TextBlockTest do
alias Mudbrick.TextBlock.Line.Part
alias Mudbrick.TextBlock.Output

test "single write is divided into lines" do
block =
TextBlock.new(
font_size: 10,
position: {400, 500}
)
|> TextBlock.write("first\nsecond\nthird", colour: {0, 0, 0})

assert block.lines == [
%Line{parts: [%Part{text: "third"}]},
%Line{parts: [%Part{text: "second"}]},
%Line{parts: [%Part{text: "first"}]}
]
end

test "writes get divided into lines and parts" do
block =
TextBlock.new(
Expand All @@ -23,74 +38,123 @@ defmodule Mudbrick.TextBlockTest do
|> TextBlock.write("""
third line\
""")
|> TextBlock.write("\nfourth", colour: {0, 1, 0})

assert block.lines == [
%Line{parts: [%Part{text: "third line"}, %Part{text: ""}]},
%Line{parts: [%Part{text: "fourth", colour: {0, 1, 0}}]},
%Line{
parts: [
%Part{text: "", colour: {0, 1, 0}},
%Part{text: "third line"},
%Part{text: ""}
]
},
%Line{parts: [%Part{text: "second line"}]},
%Line{parts: [%Part{text: "line"}, %Part{text: "first ", colour: {1, 0, 0}}]}
]
end

test "left-aligned newlines become apostrophes" do
assert [
"BT",
"/F1 10 Tf",
"12.0 TL",
"400 500 Td",
"<014C010F0116011D01B700ED00D900F400C0> Tj",
"<011600C000B500FC00F400BB01B700ED00D900F400C0> '",
"() '",
"ET"
] =
output(fn font ->
TextBlock.new(
font: font,
font_size: 10,
position: {400, 500}
)
|> TextBlock.write("""
first line
second line
""")
end)
|> operations()
describe "left-aligned" do
test "newlines become apostrophes" do
assert [
"BT",
"/F1 10 Tf",
"12.0 TL",
"400 500 Td",
"<014C010F0116011D01B700ED00D900F400C0> Tj",
"<011600C000B500FC00F400BB01B700ED00D900F400C0> '",
"() '",
"ET"
] =
output(fn font ->
TextBlock.new(
font: font,
font_size: 10,
position: {400, 500}
)
|> TextBlock.write("""
first line
second line
""")
end)
|> operations()
end

test "inline colours are written with Tjs" do
assert [
"BT",
"/F1 10 Tf",
"12.0 TL",
"400 500 Td",
"<00A5> Tj",
"1 0 0 rg",
"<00B4> Tj",
"0 1 0 rg",
"() Tj",
"<00B5> '",
"<00BB> '",
"0 0 1 rg",
"<00C0> Tj",
"ET"
] =
output(fn font ->
TextBlock.new(
font: font,
font_size: 10,
position: {400, 500}
)
|> TextBlock.write("a")
|> TextBlock.write("b", colour: {1, 0, 0})
|> TextBlock.write("\nc\nd", colour: {0, 1, 0})
|> TextBlock.write("e", colour: {0, 0, 1})
end)
|> operations()
end
end

test "right-aligned newlines become Tjs with offsets" do
assert [
"BT",
"/F1 10 Tf",
"12.0 TL",
"384.82 500.0 Td",
"<00A500A500A5> Tj",
"ET",
"BT",
"379.42 488.0 Td",
"<013801380138> Tj",
"ET",
"BT",
"306.48 476.0 Td",
"<00880055008800550088005500880055008800550088> Tj",
"ET",
"BT",
"400 464.0 Td",
"() Tj",
"ET"
] =
output(fn font ->
Mudbrick.TextBlock.new(
font: font,
font_size: 10,
position: {400, 500},
align: :right
)
|> Mudbrick.TextBlock.write("""
aaa
www
WOWOWOWOWOW
""")
end)
|> operations()
describe "right-aligned" do
test "newlines become Tjs with offsets" do
assert [
"BT",
"/F1 10 Tf",
"12.0 TL",
"384.82 500.0 Td",
"<00A500A500A5> Tj",
"ET",
"BT",
"379.42 488.0 Td",
"<013801380138> Tj",
"ET",
"BT",
"306.48 476.0 Td",
"<00880055008800550088005500880055008800550088> Tj",
"ET",
"BT",
"400 464.0 Td",
"() Tj",
"ET",
"BT",
"390.74 452.0 Td",
"<00D500D9> Tj",
"ET"
] =
output(fn font ->
Mudbrick.TextBlock.new(
font: font,
font_size: 10,
position: {400, 500},
align: :right
)
|> Mudbrick.TextBlock.write("""
aaa
www
WOWOWOWOWOW
hi\
""")
end)
|> operations()
end
end

defp operations(tb) do
Expand Down

0 comments on commit eebbe9d

Please sign in to comment.