Skip to content

Commit eebbe9d

Browse files
committed
Text block colours
1 parent 5114abf commit eebbe9d

File tree

2 files changed

+180
-78
lines changed

2 files changed

+180
-78
lines changed

lib/mudbrick/text_block.ex

Lines changed: 57 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ defmodule Mudbrick.TextBlock do
2626
defstruct operations: []
2727

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

5253
output
53-
|> Output.add(%Tj{font: font, text: first_part.text})
5454
|> then(
55-
&for %{parts: [part]} <- rest, reduce: &1 do
56-
acc -> Output.add(acc, %Apostrophe{font: font, text: part.text})
57-
end
55+
&List.foldr(first_parts, &1, fn %Line.Part{text: text, colour: colour}, acc ->
56+
acc
57+
|> colour(colour)
58+
|> add(%Tj{font: font, text: text})
59+
end)
5860
)
61+
|> then(fn output ->
62+
for %Line{parts: parts} <- other_lines, reduce: output do
63+
acc ->
64+
{_, acc} =
65+
List.foldr(parts, {false, acc}, fn
66+
%Line.Part{text: text, colour: colour}, {false, inner_acc} ->
67+
{
68+
true,
69+
inner_acc
70+
|> colour(colour)
71+
|> add(%Apostrophe{font: font, text: text})
72+
}
73+
74+
%Line.Part{text: text, colour: colour}, {true, inner_acc} ->
75+
{
76+
true,
77+
inner_acc
78+
|> colour(colour)
79+
|> add(%Tj{font: font, text: text})
80+
}
81+
end)
82+
83+
acc
84+
end
85+
end)
5986
end)
6087
|> end_block()
6188

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

80107
output
81108
|> right_offset(tb, first_part.text, 1)
82-
|> Output.add(%Tj{font: font, text: first_part.text})
109+
|> add(%Tj{font: font, text: first_part.text})
83110
|> end_block()
84111
|> then(fn output ->
85112
{_line, output} =
@@ -90,7 +117,7 @@ defmodule Mudbrick.TextBlock do
90117
acc
91118
|> start_block()
92119
|> right_offset(tb, part.text, line)
93-
|> Output.add(%Tj{font: font, text: part.text})
120+
|> add(%Tj{font: font, text: part.text})
94121
|> end_block()
95122
}
96123
end
@@ -102,28 +129,39 @@ defmodule Mudbrick.TextBlock do
102129
output.operations
103130
end
104131

105-
def add(%__MODULE__{} = output, op) do
106-
Map.update!(output, :operations, &[op | &1])
107-
end
108-
109-
def start_block(output) do
132+
defp start_block(output) do
110133
add(output, %BT{})
111134
end
112135

113-
def end_block(output) do
136+
defp end_block(output) do
114137
add(output, %ET{})
115138
end
116139

117-
def right_offset(output, tb, text, line) do
140+
defp right_offset(output, tb, text, line) do
118141
n = line - 1
119142
{x, y} = tb.position
120143

121-
Output.add(output, %Td{
144+
add(output, %Td{
122145
tx: x - Font.width(tb.font, tb.font_size, text),
123146
ty: y - leading(tb) * n
124147
})
125148
end
126149

150+
defp colour(output, {r, g, b}) do
151+
new_colour = %Rg{r: r, g: g, b: b}
152+
latest_colour = Enum.find(output.operations, &match?(%Rg{}, &1)) || %Rg{r: 0, g: 0, b: 0}
153+
154+
if latest_colour != new_colour do
155+
add(output, new_colour)
156+
else
157+
output
158+
end
159+
end
160+
161+
defp add(%__MODULE__{} = output, op) do
162+
Map.update!(output, :operations, &[op | &1])
163+
end
164+
127165
defp leading(tb) do
128166
tb.font_size * 1.2
129167
end
@@ -144,9 +182,9 @@ defmodule Mudbrick.TextBlock do
144182
def write(tb, text, opts \\ []) do
145183
Map.update!(tb, :lines, fn
146184
[] ->
147-
text
148-
|> String.split("\n")
149-
|> Enum.map(&Line.wrap(&1, opts))
185+
for text <- String.split(text, "\n"), reduce: [] do
186+
acc -> [Line.wrap(text, opts) | acc]
187+
end
150188

151189
[%Line{} = previous_line | existing_lines] ->
152190
[first_new_line_text | new_line_texts] = String.split(text, "\n")

test/mudbrick/text_block_test.exs

Lines changed: 123 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,21 @@ defmodule Mudbrick.TextBlockTest do
99
alias Mudbrick.TextBlock.Line.Part
1010
alias Mudbrick.TextBlock.Output
1111

12+
test "single write is divided into lines" do
13+
block =
14+
TextBlock.new(
15+
font_size: 10,
16+
position: {400, 500}
17+
)
18+
|> TextBlock.write("first\nsecond\nthird", colour: {0, 0, 0})
19+
20+
assert block.lines == [
21+
%Line{parts: [%Part{text: "third"}]},
22+
%Line{parts: [%Part{text: "second"}]},
23+
%Line{parts: [%Part{text: "first"}]}
24+
]
25+
end
26+
1227
test "writes get divided into lines and parts" do
1328
block =
1429
TextBlock.new(
@@ -23,74 +38,123 @@ defmodule Mudbrick.TextBlockTest do
2338
|> TextBlock.write("""
2439
third line\
2540
""")
41+
|> TextBlock.write("\nfourth", colour: {0, 1, 0})
2642

2743
assert block.lines == [
28-
%Line{parts: [%Part{text: "third line"}, %Part{text: ""}]},
44+
%Line{parts: [%Part{text: "fourth", colour: {0, 1, 0}}]},
45+
%Line{
46+
parts: [
47+
%Part{text: "", colour: {0, 1, 0}},
48+
%Part{text: "third line"},
49+
%Part{text: ""}
50+
]
51+
},
2952
%Line{parts: [%Part{text: "second line"}]},
3053
%Line{parts: [%Part{text: "line"}, %Part{text: "first ", colour: {1, 0, 0}}]}
3154
]
3255
end
3356

34-
test "left-aligned newlines become apostrophes" do
35-
assert [
36-
"BT",
37-
"/F1 10 Tf",
38-
"12.0 TL",
39-
"400 500 Td",
40-
"<014C010F0116011D01B700ED00D900F400C0> Tj",
41-
"<011600C000B500FC00F400BB01B700ED00D900F400C0> '",
42-
"() '",
43-
"ET"
44-
] =
45-
output(fn font ->
46-
TextBlock.new(
47-
font: font,
48-
font_size: 10,
49-
position: {400, 500}
50-
)
51-
|> TextBlock.write("""
52-
first line
53-
second line
54-
""")
55-
end)
56-
|> operations()
57+
describe "left-aligned" do
58+
test "newlines become apostrophes" do
59+
assert [
60+
"BT",
61+
"/F1 10 Tf",
62+
"12.0 TL",
63+
"400 500 Td",
64+
"<014C010F0116011D01B700ED00D900F400C0> Tj",
65+
"<011600C000B500FC00F400BB01B700ED00D900F400C0> '",
66+
"() '",
67+
"ET"
68+
] =
69+
output(fn font ->
70+
TextBlock.new(
71+
font: font,
72+
font_size: 10,
73+
position: {400, 500}
74+
)
75+
|> TextBlock.write("""
76+
first line
77+
second line
78+
""")
79+
end)
80+
|> operations()
81+
end
82+
83+
test "inline colours are written with Tjs" do
84+
assert [
85+
"BT",
86+
"/F1 10 Tf",
87+
"12.0 TL",
88+
"400 500 Td",
89+
"<00A5> Tj",
90+
"1 0 0 rg",
91+
"<00B4> Tj",
92+
"0 1 0 rg",
93+
"() Tj",
94+
"<00B5> '",
95+
"<00BB> '",
96+
"0 0 1 rg",
97+
"<00C0> Tj",
98+
"ET"
99+
] =
100+
output(fn font ->
101+
TextBlock.new(
102+
font: font,
103+
font_size: 10,
104+
position: {400, 500}
105+
)
106+
|> TextBlock.write("a")
107+
|> TextBlock.write("b", colour: {1, 0, 0})
108+
|> TextBlock.write("\nc\nd", colour: {0, 1, 0})
109+
|> TextBlock.write("e", colour: {0, 0, 1})
110+
end)
111+
|> operations()
112+
end
57113
end
58114

59-
test "right-aligned newlines become Tjs with offsets" do
60-
assert [
61-
"BT",
62-
"/F1 10 Tf",
63-
"12.0 TL",
64-
"384.82 500.0 Td",
65-
"<00A500A500A5> Tj",
66-
"ET",
67-
"BT",
68-
"379.42 488.0 Td",
69-
"<013801380138> Tj",
70-
"ET",
71-
"BT",
72-
"306.48 476.0 Td",
73-
"<00880055008800550088005500880055008800550088> Tj",
74-
"ET",
75-
"BT",
76-
"400 464.0 Td",
77-
"() Tj",
78-
"ET"
79-
] =
80-
output(fn font ->
81-
Mudbrick.TextBlock.new(
82-
font: font,
83-
font_size: 10,
84-
position: {400, 500},
85-
align: :right
86-
)
87-
|> Mudbrick.TextBlock.write("""
88-
aaa
89-
www
90-
WOWOWOWOWOW
91-
""")
92-
end)
93-
|> operations()
115+
describe "right-aligned" do
116+
test "newlines become Tjs with offsets" do
117+
assert [
118+
"BT",
119+
"/F1 10 Tf",
120+
"12.0 TL",
121+
"384.82 500.0 Td",
122+
"<00A500A500A5> Tj",
123+
"ET",
124+
"BT",
125+
"379.42 488.0 Td",
126+
"<013801380138> Tj",
127+
"ET",
128+
"BT",
129+
"306.48 476.0 Td",
130+
"<00880055008800550088005500880055008800550088> Tj",
131+
"ET",
132+
"BT",
133+
"400 464.0 Td",
134+
"() Tj",
135+
"ET",
136+
"BT",
137+
"390.74 452.0 Td",
138+
"<00D500D9> Tj",
139+
"ET"
140+
] =
141+
output(fn font ->
142+
Mudbrick.TextBlock.new(
143+
font: font,
144+
font_size: 10,
145+
position: {400, 500},
146+
align: :right
147+
)
148+
|> Mudbrick.TextBlock.write("""
149+
aaa
150+
www
151+
WOWOWOWOWOW
152+
153+
hi\
154+
""")
155+
end)
156+
|> operations()
157+
end
94158
end
95159

96160
defp operations(tb) do

0 commit comments

Comments
 (0)