sidenote

Archívum
A(z) „TeX/LaTeX” címke bejegyzései

Múlt héten keresett meg egy kedves barátom, a nap német szava blog szerzője, hogy egy darabig leáll a blog írásával, de szeretné, ha a bejegyzések PDF-ben is elérhetők lennének.

Hát összeálltunk. Ő fogta és lementette az összes posztot. Nagyjából* arra a formára hozta az anyagot, amiben előre megegyeztünk. Én írtam egy parsert LuaTeX-ben. És megszületett a PDF változat.

Ha hibát találtok benne, kérlek jelezzétek!

Az amúgy is zseniális blog bejegyzései mostantól A5-ös méretben, nyomtatásra optimalizáltan** elérhetők:

a nap német szava

* Azért benne hagyott közel kétszáz helytelen idézőjelet, és húsz-harminc rosszul tördelt sort, de sebaj, csak két órát basztam el a kijavításukkal.

** Már ha ki fogsz nyomtatni több, mint hétszáz oldalt. Amúgy képernyőn is jól néz ki.

0 komment
Megosztás, like stb.

Another font outline animation experiment

In this animation was created using LuaTeX, TikZ, and the glyph “a” from the Linux Libertine font.

The process which this animation resulted in was the following:

  1. Converted the font to SVG to use the path of the glyph.
  2. Split the path into the inner and outer outlines.
  3. Draw the two separate lines with (different) dashed patterns.
  4. Set increasing offset for the dashed patterns for each frame.
  5. Rasterize and create GIF using GIMP.

Sorry, no source code this time.

0 komment
Megosztás, like stb.

In one of my latest posts I mentioned a question about how to draw individual glyphs with randomized paths on TeX.SX. Today I want to share the answer I posted about two weeks ago, and some related stuff which I’ve made during and after working on the answer.

Let’s see the question first. It is quoting Donald Knuth’s article, Mathematical typography from the Bulletin of the American Mathematical Society.

Knuth: Mathematical typography, Figure 21

Randomization. I’d like to report on a little experiment I did with random numbers. One might complain that the letters I have designed are too perfect, too much like a computer, so they lack “character”. In order to counteract this, we can build a certain amount of randomness into the choices of where to put the pen when drawing each letter, and Figure 21 shows what happens. The coordinates of the key pen positions where chosen independently with a normal distribution and with increasing standard deviation, so that the third example has twice as much standard deviation as the second, the fourth has three times as much, and so on. Note that the two m’s on each line (except the first) are different, and so are the a’s and the t’s, since each letter is drawn randomly.

The question was how to achieve a similar effect in LaTeX without using MetaFont. You can read the answer below, but if you feel tl;dr skip below the long quote for my related works.

I have been thinking about this question for weeks now, and finally I think I came really close to a result you may also like. I have even tried to use Processing to solve this problem, which resulted in a nice animation as a byproduct, but it didn’t lead me closer to the solution. But back to the point…

Unfortunately the solution I’m posting, which is my best and only shot, does not support drawing the distorted glyphs as text but as drawings. Also there is some work to be done outside the context of LaTeX, but most of it is done in LaTeX (LuaTeX + TikZ).

Randomized drawing of individual glyphs

The picture above shows an undistorted glyph (character “a” on the left in the line at the top), a distorted glyph (character “a” on the right in the line at the top), a word consisting of distorted glyphs (middle line), and a special character (Omega), all these can be found in the code at the end of the answer.

Now I will describe the process I have followed to achieve these distortions. I mentioned that there is some work to be done outside of LaTeX, that is to convert a font file into SVG using FontForge. I found the solution how to do this in an answer to the question: Can we extract the points making the character from the font file?

Copy the following into a file named font2svg.pe into your “project” folder.

1
2
3
#!/usr/bin/env fontforge
Open($1)
Generate($1:t:r + ".svg")

And make a SVG file from the font you want to use (I chose cmr10) with the following command.

fontforge font2svg.pe /usr/local/texlive/2014/texmf-dist/fonts/type1/public/amsfonts/cm/cmr10.pfb

Note that the location of the font on the filesystem may vary based on your LaTeX installation and operating system you use, but this will generate an SVG file into your project folder. All is left to process the generated SVG file which contains the data (name, unicode code, width, and outline) of the glyphs, which I will describe below.

The function function read_font_data(file) takes a file name as an argument (the generated SVG file), and extracts the data of the glyphs into an associative array which can be addressed with the unicode code and contains the width and outline data of the specific character. Note that not all glyphs have width or outline data, some basic error checking is done but the code is not foolproof.

The function random_in_interval(lower_boundary, upper_boundary) takes two float arguments, and will return a random float between them. The more the boundaries converge to 1 the smaller the randomization will be. This will be used when the time comes to randomize the outline of a glyph.

The function scale_and_randomize(glyph, scale_factor, lower_boundary, upper_boundary) will take a glyph, a scale factor, a lower and upper boundary, the latter two will be used for the randomization. Scaling is needed because the default measurement unit of TikZ is centimeters (I think) and the outline data of a glyph may contain large values, which TikZ interprets as centimeters. Note that the scale factor may vary depending the font you use, and size you want.

The functions print_glyph(glyph, scale_factor, lower_boundary, upper_boundary) and return_glyph (the latter takes the same arguments) only differ in that print_glyph will pass the TikZ drawing command (using svg.path library) used to print the glyph to LaTeX, while return_glyph only returns the drawing command as a string which can be further used in Lua before passing it to LaTeX.

The remaining functions only use the previously described print_glyph and return_glyph functions to print the picture above.

That’s it. I hope this would fit your needs.

1
2
3
4
5
6
7
8
9
10
11
12
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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
% Randomized drawing of individual glyphs
% Author: István Szántai (szantaii)
% Original at: http://tex.stackexchange.com/a/197677/8844
\documentclass[10pt, a4paper]{article}

\usepackage[T1]{fontenc}

\usepackage{luacode}

\usepackage{tikz}
\usetikzlibrary{svg.path, positioning}

\pagestyle{empty}

\tikzset{%
    glyph node/.style={%
        inner sep=0pt,%
        outer sep=0pt%
    },%
    glyph outline/.style={%
        line width=0pt%
    }%
}

\begin{luacode*}
    function read_font_data(file)
        local glyphs = {}
        local fd = io.open(file, "r")
        local content = fd:read("*all")
        fd.close()
       
        for glyph in string.gmatch(content, "<glyph[^/>]*") do
            local glyph_tag = string.gsub(glyph, "\n", " ")
            local unicode = string.match(glyph_tag, "unicode=\"[^\"]*")
            local outline = string.match(glyph_tag, "d=\"[^\"]*")
            local width = string.match(glyph_tag, "horiz%-adv%-x=\"[^\"]*")
           
            if unicode ~= nil and #unicode >= 10 then
                unicode = string.sub(unicode, 10, #unicode)
            end
           
            if outline ~= nil and #outline > 4 then
                outline = string.sub(outline, 4, #outline)
            end
           
            if width ~= nil and #width >= 14 then
                width = string.sub(width, 14, #width)
            end
           
            if unicode ~= nil then
                glyphs[unicode] = {width, outline}
            end
        end
       
        return glyphs
    end
   
    -- returns a random float number between the specified boundaries (floats)
    function random_in_interval(lower_boundary, upper_boundary)
        return ((math.random() * (upper_boundary - lower_boundary)) + lower_boundary)
    end
   
    -- note: scaling is applied before randomization
    function scale_and_randomize(glyph, scale_factor, lower_boundary, upper_boundary)
        local width = glyph[1]
        local outline = glyph[2]
       
        local previous_was_number = false
        local processed_outline = ""
        local number = ""
       
        if width ~= nil then
            width = width * scale_factor
        end
       
        if outline ~= nil then
            for i = 1, #outline, 1 do
                local char = string.sub(outline, i, i)
               
                if previous_was_number then
                    if string.match(char, '%d') ~= nil or
                        char == "." then
                        number = number .. char
                    else
                        -- scale and randomize
                        number = number * scale_factor
                        number = number * random_in_interval(lower_boundary, upper_boundary)
                        number = string.format("%.3f", number)
                        processed_outline = processed_outline .. number .. char
                        number = ""
                        previous_was_number = false
                    end
                else
                    if string.match(char, '%d') ~= nil or
                        char == "-" then
                       
                        number = number .. char
                        previous_was_number = true
                    else
                        processed_outline = processed_outline .. char
                        previous_was_number = false
                    end
                end
            end
        end
       
        return {width, processed_outline}
    end
   
    function print_glyph(glyph, scale_factor, lower_boundary, upper_boundary)
        local randomized_glyph = scale_and_randomize(glyph, scale_factor, lower_boundary, upper_boundary)
        local width = randomized_glyph[1]
        local outline = randomized_glyph[2]

        if outline ~= nil then
            tex.sprint("\\filldraw[glyph outline] svg \"" .. outline .. "\";")
        end
    end
   
    function return_glyph(glyph, scale_factor, lower_boundary, upper_boundary)
        local randomized_glyph = scale_and_randomize(glyph, scale_factor, lower_boundary, upper_boundary)
        local width = randomized_glyph[1]
        local outline = randomized_glyph[2]
       
        if outline ~= nil then
            return "\\filldraw[glyph outline] svg \"" .. outline .. "\";"
        else
            return ""
        end
    end
   
    function draw_sample_glyphs(glyphs)
        tex.sprint("\\begin{tikzpicture}")
        tex.sprint("\\node[glyph node, matrix, anchor=south west] (a1) {" ..
            return_glyph(glyphs["a"], 0.05, 1, 1) ..
            "\\\\};")
        tex.sprint("\\node[glyph node, matrix, anchor=south west, right=7.5mm of a1] (a2) {" ..
            return_glyph(glyphs["a"], 0.05, 0.8, 1.2) ..
            "\\\\};")
        tex.sprint("\\end{tikzpicture}")
    end
   
    function draw_sample_text(glyphs)
        local horizontal_space = "0.5mm"
        local vertical_space = "1.25mm"
        local scale = 0.05
        local lower_boundary = 0.9
        local upper_boundary = 1.1
       
        tex.sprint("\\begin{tikzpicture}")
        tex.sprint("\\node[glyph node, matrix] (m1) {" ..
            return_glyph(glyphs["m"], scale, lower_boundary, upper_boundary) ..
            "\\\\};")
       
        tex.sprint("\\node[glyph node, matrix, right=" .. horizontal_space ..
            " of m1] (a1) {" ..
            return_glyph(glyphs["a"], scale, lower_boundary, upper_boundary) ..
            "\\\\};")
       
        tex.sprint("\\node[glyph node, matrix, right=" .. horizontal_space ..
            " of a1] (t1) {" .. "\\raisebox{" .. vertical_space .. "}{" ..
            return_glyph(glyphs["t"], scale, lower_boundary, upper_boundary) ..
            "}" .. "\\\\};")
       
        tex.sprint("\\node[glyph node, matrix, right=" .. horizontal_space ..
            " of t1] (h1) {" .. "\\raisebox{" .. vertical_space .. "}{" ..
            return_glyph(glyphs["h"], scale, lower_boundary, upper_boundary) ..
            "}" .. "\\\\};")
       
        tex.sprint("\\node[glyph node, matrix, right=" .. horizontal_space ..
            " of h1] (e1) {" ..
            return_glyph(glyphs["e"], scale, lower_boundary, upper_boundary) ..
            "\\\\};")
       
        tex.sprint("\\node[glyph node, matrix, right=" .. horizontal_space ..
            " of e1] (m2) {" ..
            return_glyph(glyphs["m"], scale, lower_boundary, upper_boundary) ..
            "\\\\};")
       
        tex.sprint("\\node[glyph node, matrix, right=" .. horizontal_space ..
            " of m2] (a2) {" ..
            return_glyph(glyphs["a"], scale, lower_boundary, upper_boundary) ..
            "\\\\};")
       
        tex.sprint("\\node[glyph node, matrix, right=" .. horizontal_space ..
            " of a2] (t2) {" .. "\\raisebox{" .. vertical_space .. "}{" ..
            return_glyph(glyphs["t"], scale, lower_boundary, upper_boundary) ..
            "}" .. "\\\\};")
       
        tex.sprint("\\node[glyph node, matrix, right=" .. horizontal_space ..
            " of t2] (i1) {" .. "\\raisebox{" .. vertical_space .. "}{" ..
            return_glyph(glyphs["i"], scale, lower_boundary, upper_boundary) ..
            "}" .. "\\\\};")
       
        tex.sprint("\\node[glyph node, matrix, right=" .. horizontal_space ..
            " of i1] (c1) {" ..
            return_glyph(glyphs["c"], scale, lower_boundary, upper_boundary) ..
            "\\\\};")
       
        tex.sprint("\\node[glyph node, matrix, right=" .. horizontal_space ..
            " of c1] (s1) {" ..
            return_glyph(glyphs["s"], scale, lower_boundary, upper_boundary) ..
            "\\\\};")
        tex.sprint("\\end{tikzpicture}")
    end
   
    function draw_sample_glyph(glyphs)
        tex.sprint("\\begin{tikzpicture}")
        print_glyph(glyphs["&#x3a9;"], 0.05, 0.95, 1.05)
        tex.sprint("\\end{tikzpicture}")
    end
   
    function main()
        local cmr10_glyphs = {}
       
        math.randomseed(os.time())
       
        cmr10_glyphs = read_font_data("cmr10.svg")
       
        tex.sprint("\\noindent")
        draw_sample_glyphs(cmr10_glyphs)
        tex.sprint("\\\\[2cm]")
        draw_sample_text(cmr10_glyphs)
        tex.sprint("\\\\[2cm]")
        draw_sample_glyph(cmr10_glyphs)
    end
\end{luacode*}

\begin{document}
\luadirect{main()}
\end{document}

Nice, isn’t it?

I’ve created an animation in LaTeX (clarification later) using this code, which makes the letter “a” wibbly-wobbly. There is also a video which shows the same letter but only the outline is drawn, but in my opinion it doesn’t look that good: Glyph distortion – YouTube.

These videos are also available on Vimeo: Glyph distortion on Vimeo, Glyph distortion #2 on Vimeo.

The second animation (also made in LaTeX) draws a big letter “a” stacking only outlines of it on top of each other which it gives a hollow like look.

This video is also available on Vimeo: Font outline animation #2 on Vimeo.

Only one question remains, how did I made these animations in LaTeX? I just built upon the code above, and rendered a long PDF which’s each page is a frame of the animation. After that I only followed some steps I’ve described before.

0 komment
Megosztás, like stb.

As a follow-up for my last post hereby I present a Creeper (right) in (Lua)LaTeX.

Creeper Creeper

Image source (left): File:Creeper.png – Minecraft Wiki

1
2
3
4
5
6
7
8
9
10
11
12
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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
% Creeper
% Author: István Szántai (szantaii)
\documentclass{article}

\usepackage{luacode}

\usepackage{xcolor}

\usepackage{tikz}
\usepackage{tikz-3dplot}

\usepackage[active, tightpage]{preview}
\PreviewEnvironment{tikzpicture}
\setlength{\PreviewBorder}{1cm}

\definecolor{creeper_white}{HTML}{9AD78E}
\definecolor{creeper_lightgreen}{HTML}{5ED04C}
\definecolor{creeper_green}{HTML}{00A500}
\definecolor{creeper_darkgreen}{HTML}{255522}

\begin{luacode*}
    function draw_coordinate_system()
        tex.sprint("\\draw[white!50!gray,thick,->] (0,0,0) -- " ..
            "(3,0,0) node[text=white!50!gray,anchor=north east]{$x$};")
        tex.sprint("\\draw[white!50!gray,thick,->] (0,0,0) -- " ..
            "(0,3,0) node[text=white!50!gray,anchor=west]{$y$};")
        tex.sprint("\\draw[white!50!gray,thick,->] (0,0,0) -- " ..
            "(0,0,3) node[text=white!50!gray,anchor=south]{$z$};")
    end
   
    function matrix_scalar_multiplication(matrix, scalar)
        local rows = #matrix
        local cols = #matrix[1]
        local tmp_matrix = {}
       
        for i = 1, rows do
            tmp_matrix[i] = {}
            for j = 1, cols do
                tmp_matrix[i][j] = matrix[i][j] * scalar
            end
        end
       
        return tmp_matrix
    end
   
    function shift_coordinates(matrix, array)
        local matrix_rows = #matrix
        local matrix_cols = #matrix[1]
        local array_length = #array
        local tmp_matrix = {}
       
        if matrix_cols == array_length then
            for i = 1, matrix_rows do
                tmp_matrix[i] = {}
                for j = 1, matrix_cols do
                    tmp_matrix[i][j] = matrix[i][j] + array[j]
                end
            end
           
            return tmp_matrix
        else
            return nil
        end
    end
   
    function tikzcube(x, y, z, color)
        local side_1 = {{1, 1, -1},
            {-1, 1, -1},
            {-1, -1, -1},
            {1, -1, -1}}
        local side_2 = {{-1, 1, -1},
            {-1, 1, 1},
            {-1, -1, 1},
            {-1, -1, -1}}
        local side_3 = {{-1, -1, -1},
            {1, -1, -1},
            {1, -1, 1},
            {-1, -1, 1}}
        local side_4 = {{1, 1, -1},
            {-1, 1, -1},
            {-1, 1, 1},
            {1, 1, 1}}
        local side_5 = {{1, -1, -1},
            {1, 1, -1},
            {1, 1, 1},
            {1, -1, 1}}
        local side_6 = {{1, 1, 1},
            {-1, 1, 1},
            {-1, -1, 1},
            {1, -1, 1}}
        local cube_sides = {side_1, side_2, side_3, side_4, side_5, side_6}
        local tex_cube = ""
       
        for i = 1, #cube_sides do
            tex_cube = tex_cube .. "\\draw[ultra thin, fill=" .. color .. "] "
           
            local current_side = matrix_scalar_multiplication(cube_sides[i], 0.5)
            current_side = shift_coordinates(current_side, {x, y, z})
           
            local current_side_rows = #current_side
            local current_side_cols = #current_side[1]
           
            for j = 1, current_side_rows do
                for k = 1, current_side_cols do
                    if k == 1 then
                        tex_cube = tex_cube .. "("
                    end
                   
                    tex_cube = tex_cube .. current_side[j][k]
                   
                    if k ~= current_side_cols then
                        tex_cube = tex_cube .. ", "
                    else
                        tex_cube = tex_cube .. ") -- "
                    end
                end
            end
           
            tex_cube = tex_cube .. "cycle;"
           
        end
       
        tex.sprint(tex_cube)
    end
   
    function draw_head(x_pos, y_pos, z_pos, colors_array)
        for x = x_pos, x_pos + 7, 1 do
            for y = y_pos, y_pos + 7, 1 do
                for z = z_pos, z_pos + 7, 1 do
                    if x == x_pos or x == x_pos + 7 or
                        y == y_pos or y == y_pos + 7 or
                        z == z_pos or z == z_pos + 7 then
                       
                        local color_index = math.random(1, #colors_array - 1)
                        tikzcube(x, y, z, colors_array[color_index])
                       
                        if x == x_pos + 7 and
                            (z == z_pos + 4 and
                            (y == y_pos + 2 or y == y_pos + 5)) or
                            (z == z_pos + 2 and
                            (y == y_pos + 3 or y == y_pos + 4)) or
                            (z == z_pos + 1 and
                            (y > 1 and y < 6)) then
                           
                            tikzcube(x, y, z, "black")
                        end
                       
                        if x == x_pos + 7 and
                            ((z == z_pos + 5 and
                            ((y > y_pos and y < y_pos + 3) or
                            (y > y_pos + 4 and y < y_pos + 7))) or
                            (z == z_pos + 4 and
                            (y == y_pos + 1 or y == y_pos + 6)) or
                            (z == z_pos + 3 and
                            (y == y_pos + 3 or y == y_pos + 4)) or
                            (z == z_pos + 2 and
                            (y == y_pos + 2 or y == y_pos + 5)) or
                            (z == z_pos and
                            (y == y_pos + 2 or y == y_pos + 5))) then
                           
                            tikzcube(x, y, z, colors_array[#colors_array])
                        end
                    end
                end
            end
        end
    end
   
    function draw_leg(x_pos, y_pos, z_pos, colors_array)
        for x = x_pos, x_pos + 3, 1 do
            for y = y_pos, y_pos + 7, 1 do
                for z = z_pos, z_pos + 5, 1 do
                    if x == x_pos or x == x_pos + 3 or
                        y == y_pos or y == y_pos + 7 or
                        z == z_pos or z == z_pos + 5 then
                       
                        local color_index = math.random(1, #colors_array - 1)
                        tikzcube(x, y, z, colors_array[color_index])
                       
                        if x == x_pos + 3 and
                            ((z == z_pos + 1 and
                            (y == y_pos or y == y_pos + 2 or
                            y == y_pos + 4 or y == y_pos + 6)) or
                            (z == z_pos and
                            (y == y_pos + 1 or y == y_pos + 3 or
                            y == y_pos + 5 or y == y_pos + 7))) then
                           
                            tikzcube(x, y, z, colors_array[#colors_array])
                        end
                       
                        if x == x_pos + 3 and
                            ((z == z_pos + 1 and
                            (y == y_pos + 1 or y == y_pos + 3 or
                            y == y_pos + 5 or y == y_pos + 7)) or
                            (z == z_pos and
                            (y == y_pos or y == y_pos + 2 or
                            y == y_pos + 4 or y == y_pos + 6))) then
                           
                            tikzcube(x, y, z, "black")
                        end
                    end
                end
            end
        end
    end
   
    function draw_bodypart(x_pos, y_pos, z_pos, x_length, y_length, z_length, colors_array)
        local color
        for x = x_pos, x_pos + x_length - 1, 1 do
            for y = y_pos, y_pos + y_length - 1, 1 do
                for z = z_pos, z_pos + z_length - 1, 1 do
                    if x == x_pos or x == x_pos + x_length - 1 or
                        y == y_pos or y == y_pos + y_length - 1 or
                        z == z_pos or z == z_pos + z_length - 1 then
                       
                        local color_index = math.random(1, #colors_array)
                        tikzcube(x, y, z, colors_array[color_index])
                    end
                end
            end
        end
    end
   
    function draw_creeper(x_rotation, z_rotation)
        local creeper_colors = {"creeper_white",
            "creeper_lightgreen",
            "creeper_green",
            "creeper_darkgreen"}
       
        tex.sprint("\\tdplotsetmaincoords{" .. x_rotation .. "}{" .. z_rotation .. "}")
        tex.sprint("\\begin{tikzpicture}[tdplot_main_coords]")
       
        math.randomseed(os.time())
       
        draw_leg(-2, 0, -18, creeper_colors)
        draw_leg(6, 0, -18, creeper_colors)
        draw_bodypart(2, 0, -12, 4, 8, 12, creeper_colors)
        draw_head(0, 0, 0, creeper_colors)
        -- draw_coordinate_system()
       
        tex.sprint("\\end{tikzpicture}")
    end
\end{luacode*}

\begin{document}
\luadirect{draw_creeper(66, 135)}
\end{document}
0 komment
Megosztás, like stb.

In the last couple of months I’ve watched some videos of Achievement Hunter’s Let’s Play Minecraft videos where were some encounters with Endermen. I was really fascinated by these creatures since they can teleport, demand respect, mysterious, etc.

I have been “playing” with LaTeX, especially with LuaTeX, recently, and I wanted to make a 3D-like picture. So I have chosen tikz‑3dplot and LuaTeX to draw an Enderman. (I think this is totally drawable with pure TikZ (syntax), therefore compilable with pdflatex, etc., but I found making this using LuaTeX much more simple.)

Enderman Enderman

Image source (left): File:Enderman normal.png – Minecraft Wiki

My drawing (right) is not accurate by many aspects, but I’ve achieved what I wanted.

1
2
3
4
5
6
7
8
9
10
11
12
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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
% Enderman
% Author: István Szántai (szantaii)
\documentclass{article}

\usepackage{luacode}

\usepackage{xcolor}

\usepackage{tikz}
\usepackage{tikz-3dplot}

\usepackage[active, tightpage]{preview}
\PreviewEnvironment{tikzpicture}
\setlength{\PreviewBorder}{1cm}

\definecolor{endermanblack}{HTML}{000000}
\definecolor{endermangray}{HTML}{161616}
%\definecolor{endermanpurple}{HTML}{CC00FA}
%\definecolor{endermanlightpurple}{HTML}{E079FA}
\definecolor{endermanpurple}{HTML}{FF9EFF}
\definecolor{endermanlightpurple}{HTML}{FFC9FF}
\definecolor{particlecolor}{HTML}{DF4AF8}

\begin{luacode*}
    function draw_coordinate_system()
        tex.sprint("\\draw[white!50!gray,thick,->] (0,0,0) -- " ..
            "(3,0,0) node[text=white!50!gray,anchor=north east]{$x$};")
        tex.sprint("\\draw[white!50!gray,thick,->] (0,0,0) -- " ..
            "(0,3,0) node[text=white!50!gray,anchor=west]{$y$};")
        tex.sprint("\\draw[white!50!gray,thick,->] (0,0,0) -- " ..
            "(0,0,3) node[text=white!50!gray,anchor=south]{$z$};")
    end
   
    function tikzcube(x, y, z, color)
        --[[
        \draw[fill=red] (0.5, 0.5, -0.5) -- (-0.5, 0.5, -0.5) -- (-0.5, -0.5, -0.5) -- (0.5, -0.5, -0.5) -- cycle;
        \draw[fill=red] (-0.5, 0.5, -0.5) -- (-0.5, 0.5, 0.5) -- (-0.5, -0.5, 0.5) -- (-0.5, -0.5, -0.5) -- cycle;
        \draw[fill=red] (-0.5, -0.5, -0.5) -- (0.5, -0.5, -0.5) -- (0.5, -0.5, 0.5) -- (-0.5, -0.5, 0.5) -- cycle;
        \draw[fill=red] (0.5, 0.5, -0.5) -- (-0.5, 0.5, -0.5) -- (-0.5, 0.5, 0.5) -- (0.5, 0.5, 0.5) -- cycle;
        \draw[fill=red] (0.5, -0.5, -0.5) -- (0.5, 0.5, -0.5) -- (0.5, 0.5, 0.5) -- (0.5, -0.5, 0.5) -- cycle;
        \draw[fill=red] (0.5, 0.5, 0.5) -- (-0.5, 0.5, 0.5) -- (-0.5, -0.5, 0.5) -- (0.5, -0.5, 0.5) -- cycle;
        ]]

        local cube = ""
       
        cube = cube .. "\\draw[ultra thin, fill=" .. color .. "]" ..
            "(0.5 + " .. x .. ", 0.5 + " .. y .. ", -0.5 + " .. z .. ") -- " ..
            "(-0.5 + " .. x .. ", 0.5 + " .. y .. ", -0.5 + " .. z .. ") -- " ..
            "(-0.5 + " .. x .. ", -0.5 + " .. y .. ", -0.5 + " .. z .. ") -- " ..
            "(0.5 + " .. x .. ", -0.5 + " .. y .. ", -0.5 + " .. z .. ") -- cycle;"
        cube = cube .. "\\draw[ultra thin, fill=" .. color .. "]" ..
            "(-0.5 + " .. x .. ", 0.5 + " .. y .. ", -0.5 + " .. z .. ") -- " ..
            "(-0.5 + " .. x .. ", 0.5 + " .. y .. ", 0.5 + " .. z .. ") -- " ..
            "(-0.5 + " .. x .. ", -0.5 + " .. y .. ", 0.5 + " .. z .. ") -- " ..
            "(-0.5 + " .. x .. ", -0.5 + " .. y .. ", -0.5 + " .. z .. ") -- cycle;"
        cube = cube .. "\\draw[ultra thin, fill=" .. color .. "]" ..
            "(-0.5 + " .. x .. ", -0.5 + " .. y .. ", -0.5 + " .. z .. ") -- " ..
            "(0.5 + " .. x .. ", -0.5 + " .. y .. ", -0.5 + " .. z .. ") -- " ..
            "(0.5 + " .. x .. ", -0.5 + " .. y .. ", 0.5 + " .. z .. ") -- " ..
            "(-0.5 + " .. x .. ", -0.5 + " .. y .. ", 0.5 + " .. z .. ") -- cycle;"
        cube = cube .. "\\draw[ultra thin, fill=" .. color .. "]" ..
            "(0.5 + " .. x .. ", 0.5 + " .. y .. ", -0.5 + " .. z .. ") -- " ..
            "(-0.5 + " .. x .. ", 0.5 + " .. y .. ", -0.5 + " .. z .. ") -- " ..
            "(-0.5 + " .. x .. ", 0.5 + " .. y .. ", 0.5 + " .. z .. ") -- " ..
            "(0.5 + " .. x .. ", 0.5 + " .. y .. ", 0.5 + " .. z .. ") -- cycle;"
        cube = cube .. "\\draw[ultra thin, fill=" .. color .. "]" ..
            "(0.5 + " .. x .. ", -0.5 + " .. y .. ", -0.5 + " .. z .. ") -- " ..
            "(0.5 + " .. x .. ", 0.5 + " .. y .. ", -0.5 + " .. z .. ") -- " ..
            "(0.5 + " .. x .. ", 0.5 + " .. y .. ", 0.5 + " .. z .. ") -- " ..
            "(0.5 + " .. x .. ", -0.5 + " .. y .. ", 0.5 + " .. z .. ") -- cycle;"
        cube = cube .. "\\draw[ultra thin, fill=" .. color .. "]" ..
            "(0.5 + " .. x .. ", 0.5 + " .. y .. ", 0.5 + " .. z .. ") -- " ..
            "(-0.5 + " .. x .. ", 0.5 + " .. y .. ", 0.5 + " .. z .. ") -- " ..
            "(-0.5 + " .. x .. ", -0.5 + " .. y .. ", 0.5 + " .. z .. ") -- " ..
            "(0.5 + " .. x .. ", -0.5 + " .. y .. ", 0.5 + " .. z .. ") -- cycle;"
       
        tex.sprint(cube)
    end
   
    function draw_head(x_pos, y_pos, z_pos)
        local color
        for x = x_pos, x_pos + 7, 1 do
            for y = y_pos, y_pos + 7, 1 do
                for z = z_pos, z_pos + 6, 1 do
                    if (x == x_pos or x == x_pos + 7 or
                        y == y_pos or y == y_pos + 7 or
                        z == z_pos or z == z_pos + 6) and
                        not (x == x_pos + 7 and y > y_pos and y < y_pos + 7 and z == z_pos) then
                       
                        if x == x_pos + 7 and
                            (y == y_pos or y == y_pos + 2 or
                            y == y_pos + 5 or y == y_pos + 7) and
                            z == z_pos + 2 then
                           
                            tikzcube(x, y, z, "endermanlightpurple")
                        elseif x == x_pos + 7 and
                            (y == y_pos + 1 or y == y_pos + 6) and
                            z == z_pos + 2 then
                           
                            tikzcube(x, y, z, "endermanpurple")
                        else
                            if math.random(0, 8) < 6 then
                                color = "endermangray"
                            else
                                color = "endermanblack"
                            end
                            tikzcube(x, y, z, color)
                        end
                    end
                end
            end
        end
    end
   
    function draw_bodypart(x_pos, y_pos, z_pos, x_length, y_length, z_length)
        local color
        for x = x_pos, x_pos + x_length - 1, 1 do
            for y = y_pos, y_pos + y_length - 1, 1 do
                for z = z_pos, z_pos + z_length - 1, 1 do
                    if x == x_pos or x == x_pos + x_length - 1 or
                        y == y_pos or y == y_pos + y_length - 1 or
                        z == z_pos or z == z_pos + z_length - 1 then
                       
                        if math.random(0, 8) < 6 then
                            color = "endermangray"
                        else
                            color = "endermanblack"
                        end
                        tikzcube(x, y, z, color)
                    end
                end
            end
        end
    end
   
    function draw_particles(x_min, x_max, y_min, y_max, z_min, z_max)
        local x
        local y
        local z
        local black_amount
        local particle_size
        local particle_scale
        local particle_count = math.random(30, 40)
        local particle
       
        for i = 1, particle_count, 1 do
           
            x = math.random(x_min, x_max)
            y = math.random(y_min, y_max)
            z = math.random(z_min, z_max)
           
            particle_size = math.random(1, 8)
            particle_scale = math.random(20, 100) / 100
            black_amount = math.random(0, 25)
           
            tex.sprint("\\tdplottransformmainscreen{" .. x .. "}{" .. y .. "}{" .. z .. "}")
            for i = 0, particle_size - 1, 1 do
                for j = 0, particle_size - 1, 1 do
                    if math.random(0, 1) == 0 and
                        ((i ~= 0 and j ~= 0) and
                        (i ~= particle_size - 1 and j ~= 0) and
                        (j ~= particle_size - 1 and i ~= 0) and
                        (i ~= particle_size - 1 and j ~= particle_size - 1)) then
                       
                        particle = "\\filldraw[black!" .. black_amount ..
                            "!particlecolor, tdplot_screen_coords] (" ..
                            i * particle_scale * 0.25 .. "+\\tdplotresx, " ..
                            j * particle_scale * 0.25 .. "+\\tdplotresy) " ..
                            "rectangle +(" .. particle_scale .. "*0.25, " ..
                            particle_scale .. "*0.25);"
                       
                        tex.sprint(particle)
                    end
                end
            end
        end
    end
   
    function draw_enderman(x_rotation, z_rotation)
        tex.sprint("\\tdplotsetmaincoords{" .. x_rotation .. "}{" .. z_rotation .. "}")
        tex.sprint("\\begin{tikzpicture}[tdplot_main_coords]")
       
        math.randomseed(os.time())
       
        draw_bodypart(3, -2, -30, 2, 2, 30) -- right arm
        draw_bodypart(3, 1, -42, 2, 2, 30) -- right leg
        draw_bodypart(3, 5, -42, 2, 2, 30) -- left leg
        draw_bodypart(2, 0, -12, 4, 8, 12) -- body
        draw_bodypart(3, 8, -30, 2, 2, 30)-- left arm
        draw_head(0, 0, 0) -- head
       
        draw_particles(-10, 10, -10, 10, -44, 10)
        -- draw_coordinate_system()
       
        tex.sprint("\\end{tikzpicture}")
    end
\end{luacode*}

\begin{document}
\luadirect{draw_enderman(70, 130)}
\end{document}

Copy the code, save with UTF-8 (without BOM) encoding, and compile using lualatex.

lualatex filename.tex

Update ~16:00 CEST on 21 July 2014:

Yesterday I’ve sent in this code as an example for TeXample.net. I got an answer from the site maintainer including a small improvement suggestion, namely that I should try algorithmize the drawing of a single (TikZ) cube (instead of concatenating a bunch of strings and make the TeX engine do the calculations). So I did, see the code below. (More details below the code.)

1
2
3
4
5
6
7
8
9
10
11
12
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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
% Enderman
% Author: István Szántai (szantaii)
\documentclass{article}

\usepackage{luacode}

\usepackage{xcolor}

\usepackage{tikz}
\usepackage{tikz-3dplot}

\usepackage[active, tightpage]{preview}
\PreviewEnvironment{tikzpicture}
\setlength{\PreviewBorder}{1cm}

\definecolor{endermanblack}{HTML}{000000}
\definecolor{endermangray}{HTML}{161616}
%\definecolor{endermanpurple}{HTML}{CC00FA}
%\definecolor{endermanlightpurple}{HTML}{E079FA}
\definecolor{endermanpurple}{HTML}{FF9EFF}
\definecolor{endermanlightpurple}{HTML}{FFC9FF}
\definecolor{particlecolor}{HTML}{DF4AF8}

\begin{luacode*}
    function draw_coordinate_system()
        tex.sprint("\\draw[white!50!gray,thick,->] (0,0,0) -- " ..
            "(3,0,0) node[text=white!50!gray,anchor=north east]{$x$};")
        tex.sprint("\\draw[white!50!gray,thick,->] (0,0,0) -- " ..
            "(0,3,0) node[text=white!50!gray,anchor=west]{$y$};")
        tex.sprint("\\draw[white!50!gray,thick,->] (0,0,0) -- " ..
            "(0,0,3) node[text=white!50!gray,anchor=south]{$z$};")
    end
   
    function matrix_scalar_multiplication(matrix, scalar)
        local rows = #matrix
        local cols = #matrix[1]
        local tmp_matrix = {}
       
        for i = 1, rows do
            tmp_matrix[i] = {}
            for j = 1, cols do
                tmp_matrix[i][j] = matrix[i][j] * scalar
            end
        end
       
        return tmp_matrix
    end
   
    function shift_coordinates(matrix, array)
        local matrix_rows = #matrix
        local matrix_cols = #matrix[1]
        local array_length = #array
        local tmp_matrix = {}
       
        if matrix_cols == array_length then
            for i = 1, matrix_rows do
                tmp_matrix[i] = {}
                for j = 1, matrix_cols do
                    tmp_matrix[i][j] = matrix[i][j] + array[j]
                end
            end
           
            return tmp_matrix
        else
            return nil
        end
    end
   
    function tikzcube(x, y, z, color)
        local side_1 = {{1, 1, -1},
            {-1, 1, -1},
            {-1, -1, -1},
            {1, -1, -1}}
        local side_2 = {{-1, 1, -1},
            {-1, 1, 1},
            {-1, -1, 1},
            {-1, -1, -1}}
        local side_3 = {{-1, -1, -1},
            {1, -1, -1},
            {1, -1, 1},
            {-1, -1, 1}}
        local side_4 = {{1, 1, -1},
            {-1, 1, -1},
            {-1, 1, 1},
            {1, 1, 1}}
        local side_5 = {{1, -1, -1},
            {1, 1, -1},
            {1, 1, 1},
            {1, -1, 1}}
        local side_6 = {{1, 1, 1},
            {-1, 1, 1},
            {-1, -1, 1},
            {1, -1, 1}}
        local cube_sides = {side_1, side_2, side_3, side_4, side_5, side_6}
        local tex_cube = ""
       
        for i = 1, #cube_sides do
            tex_cube = tex_cube .. "\\draw[ultra thin, fill=" .. color .. "] "
           
            local current_side = matrix_scalar_multiplication(cube_sides[i], 0.5)
            current_side = shift_coordinates(current_side, {x, y, z})
           
            local current_side_rows = #current_side
            local current_side_cols = #current_side[1]
           
            for j = 1, current_side_rows do
                for k = 1, current_side_cols do
                    if k == 1 then
                        tex_cube = tex_cube .. "("
                    end
                   
                    tex_cube = tex_cube .. current_side[j][k]
                   
                    if k ~= current_side_cols then
                        tex_cube = tex_cube .. ", "
                    else
                        tex_cube = tex_cube .. ") -- "
                    end
                end
            end
           
            tex_cube = tex_cube .. "cycle;"
           
        end
       
        tex.sprint(tex_cube)
    end
   
    function draw_head(x_pos, y_pos, z_pos)
        local color
        for x = x_pos, x_pos + 7, 1 do
            for y = y_pos, y_pos + 7, 1 do
                for z = z_pos, z_pos + 6, 1 do
                    if (x == x_pos or x == x_pos + 7 or
                        y == y_pos or y == y_pos + 7 or
                        z == z_pos or z == z_pos + 6) and
                        not (x == x_pos + 7 and y > y_pos and y < y_pos + 7 and z == z_pos) then
                       
                        if x == x_pos + 7 and
                            (y == y_pos or y == y_pos + 2 or
                            y == y_pos + 5 or y == y_pos + 7) and
                            z == z_pos + 2 then
                           
                            tikzcube(x, y, z, "endermanlightpurple")
                        elseif x == x_pos + 7 and
                            (y == y_pos + 1 or y == y_pos + 6) and
                            z == z_pos + 2 then
                           
                            tikzcube(x, y, z, "endermanpurple")
                        else
                            if math.random(0, 8) < 6 then
                                color = "endermangray"
                            else
                                color = "endermanblack"
                            end
                            tikzcube(x, y, z, color)
                        end
                    end
                end
            end
        end
    end
   
    function draw_bodypart(x_pos, y_pos, z_pos, x_length, y_length, z_length)
        local color
        for x = x_pos, x_pos + x_length - 1, 1 do
            for y = y_pos, y_pos + y_length - 1, 1 do
                for z = z_pos, z_pos + z_length - 1, 1 do
                    if x == x_pos or x == x_pos + x_length - 1 or
                        y == y_pos or y == y_pos + y_length - 1 or
                        z == z_pos or z == z_pos + z_length - 1 then
                       
                        if math.random(0, 8) < 6 then
                            color = "endermangray"
                        else
                            color = "endermanblack"
                        end
                        tikzcube(x, y, z, color)
                    end
                end
            end
        end
    end
   
    function draw_particles(x_min, x_max, y_min, y_max, z_min, z_max)
        local x
        local y
        local z
        local black_amount
        local particle_size
        local particle_scale
        local particle_count = math.random(30, 40)
        local particle
       
        for i = 1, particle_count, 1 do
           
            x = math.random(x_min, x_max)
            y = math.random(y_min, y_max)
            z = math.random(z_min, z_max)
           
            particle_size = math.random(1, 8)
            particle_scale = math.random(20, 100) / 100
            black_amount = math.random(0, 25)
           
            tex.sprint("\\tdplottransformmainscreen{" .. x .. "}{" .. y .. "}{" .. z .. "}")
            for i = 0, particle_size - 1, 1 do
                for j = 0, particle_size - 1, 1 do
                    if math.random(0, 1) == 0 and
                        ((i ~= 0 and j ~= 0) and
                        (i ~= particle_size - 1 and j ~= 0) and
                        (j ~= particle_size - 1 and i ~= 0) and
                        (i ~= particle_size - 1 and j ~= particle_size - 1)) then
                       
                        particle = "\\filldraw[black!" .. black_amount ..
                            "!particlecolor, tdplot_screen_coords] (" ..
                            i * particle_scale * 0.25 .. "+\\tdplotresx, " ..
                            j * particle_scale * 0.25 .. "+\\tdplotresy) " ..
                            "rectangle +(" .. particle_scale .. "*0.25, " ..
                            particle_scale .. "*0.25);"
                       
                        tex.sprint(particle)
                    end
                end
            end
        end
    end
   
    function draw_enderman(x_rotation, z_rotation)
        tex.sprint("\\tdplotsetmaincoords{" .. x_rotation .. "}{" .. z_rotation .. "}")
        tex.sprint("\\begin{tikzpicture}[tdplot_main_coords]")
       
        math.randomseed(os.time())
       
        draw_bodypart(3, -2, -30, 2, 2, 30) -- right arm
        draw_bodypart(3, 1, -42, 2, 2, 30) -- right leg
        draw_bodypart(3, 5, -42, 2, 2, 30) -- left leg
        draw_bodypart(2, 0, -12, 4, 8, 12) -- body
        draw_bodypart(3, 8, -30, 2, 2, 30)-- left arm
        draw_head(0, 0, 0) -- head
       
        draw_particles(-10, 10, -10, 10, -44, 10)
        -- draw_coordinate_system()
       
        tex.sprint("\\end{tikzpicture}")
    end
\end{luacode*}

\begin{document}
\luadirect{draw_enderman(70, 130)}
\end{document}

I have added two new functions (matrix_scalar_multiplication, shift_coordinates), and rewrote the tikzcube function, so now the LuaTeX engine makes all necessary calculations instead of the TeX (TikZ?) engine which is considerably slower. Let’s see the numbers.

I’ve measured the compilation time of the original and the improved code. I made 20–20 “dry” compilations (no previous aux, log or pdf files). The average compilation time of the original code was 25.35 seconds while the improved code’s average was only 18.30 seconds. This means that the improved version is 25% faster than the previous one. Stefan, thanks for your suggestion!

1 komment
Megosztás, like stb.

The other day I thought about that it would be nice to program Conway’s Game of Life in LaTeX and create an animated PDF output. You could say that implementing the Game of Life is pretty easy. It is. But not in LaTeX, at least not for me. After using LaTeX for years I still find it hard to understand some codes and especially writing programs in “pure LaTeX”.

Since I use PGF/TikZ fairly often I decided to use pgfmath for the implementation. Pretty soon I got stuck, I needed to assign values to array elements, and I didn’t know how to overcome this problem in LaTeX. So I decided to ask a question on TeX.SX. This is the point that things get interesting.

I asked a question with the title “Assign value to array element (PGF/TikZ)”, I also wrote that my aim was to program Conway’s Game of Life in LaTeX, and posted my initial code. Before long two answers came, and the question was renamed to “Programming Conway’s Game of Life in LaTeX”. The first answer implemented the Game of Life in LaTeX, the second implemented it in LuaTeX. I was stunned. Especially from the LaTeX implementation. I didn’t – and still don’t – understand the code. :) At the end I decided I should get acquainted with LuaTeX, and write my own solution in it:

Glider

1
2
3
4
5
6
7
8
9
10
11
12
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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
\documentclass{article}
\usepackage[a0paper]{geometry}

\usepackage{luacode}
\usepackage{animate}
\usepackage{tikz}
\usepackage{xcolor}
\usepackage[active, tightpage]{preview}
\PreviewEnvironment{animateinline
}
%\PreviewEnvironment{tikzpicture}

\tikzset{%
    cellframe/.style={%
        minimum size=5mm,%
        draw,%
        fill=white,%
        fill opacity=0%
    }%
}

\tikzset{%
    alivecell/.style={%
        circle,%
        inner sep=0pt,%
        minimum size=4mm,%
        fill=black%
    }%
}

\setlength{\PreviewBorder}{5mm}

\begin{document}

\begin{luacode*}
    iterations = 36
   
    grid = {{0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 1, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 1, 0, 0, 0},
        {0, 0, 0, 1, 1, 1, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0}}
\end{luacode*}

\begin{luacode*}
    function evolve(grid)
        local temp = {}
        local gridsize = #grid
       
        for i = 1, gridsize do
            temp[i] = {}
            for j = 1, gridsize do
                temp[i][j] = 0
            end
        end
       
        for i = 1, gridsize do
            for j = 1, gridsize do
               
                iminus = i - 1
                iplus = i + 1
                jminus = j - 1
                jplus = j + 1
               
                if iminus == 0 then
                    iminus = gridsize
                end
               
                if iplus == gridsize + 1 then
                    iplus = 1
                end
               
                if jminus == 0 then
                    jminus = gridsize
                end
               
                if jplus == gridsize + 1 then
                    jplus = 1
                end
               
                neighbourcount = grid[iminus][jminus] +
                    grid[iminus][j] +
                    grid[iminus][jplus] +
                    grid[i][jminus] +
                    grid[i][jplus] +
                    grid[iplus][jminus] +
                    grid[iplus][j] +
                    grid[iplus][jplus]
               
                if (grid[i][j] == 1 and (neighbourcount == 2 or neighbourcount == 3)) or (grid[i][j] == 0 and neighbourcount == 3) then
                    temp[i][j] = 1
                else
                    temp[i][j] = 0
                end
            end
        end
       
        return temp
    end
   
    function display(grid)
        local gridsize = #grid
       
       
        for i = 1, gridsize do
            for j = 1, gridsize do
                tex.sprint([[\node[cellframe] at (]])
                tex.sprint((i - 1) * 5)
                tex.sprint([[mm,]])
                tex.sprint(-((j - 1) * 5))
                tex.sprint([[mm){0};]])
               
                if grid[j][i] == 1 then
                    tex.sprint([[\node[alivecell] at (]])
                    tex.sprint((i - 1) * 5)
                    tex.sprint([[mm,]])
                    tex.sprint(-((j - 1) * 5))
                    tex.sprint([[mm){1};]])
                end
            end
        end
    end
   
    function animate(grid, iterations)
        for i = 1, iterations - 1 do
            display(grid)
            tex.sprint([[\newframe]])
            grid = evolve(grid)
        end
        display(grid)
    end
   
    function frames(grid, iterations)
        for i = 1, iterations - 1 do
            tex.sprint([[\begin{tikzpicture}]])
           
            display(grid)
            grid = evolve(grid)
           
            tex.sprint([[\end{tikzpicture}]])
            tex.sprint([[\clearpage]])
        end
       
        tex.sprint([[\begin{tikzpicture}]])
        display(grid)
        tex.sprint([[\end{tikzpicture}]])
    end
\end{luacode*}

\noindent\begin{animateinline}[autoplay,loop,
begin={\begin{tikzpicture}[scale=1
]},
end={\end{tikzpicture}}]{5}
    \luadirect{animate(grid, iterations)}
\end{animateinline
}
%\noindent\luadirect{frames(grid, iterations)}

\end{document}

Note: More information about my implementation is available in my answer on TeX.SX.

It turned out to be pretty easy to do this in LuaTeX, however I struggled with the modulus operator (%) and printing from Lua to TeX. Still it became a pretty nice solution, I think.

Last but not least I’m very grateful for the answers on my question. Here are some nice outputs of jfbu’s and JLDiaz’s answers (if you have a TeX.SX registration then please post an upvote on them because they are really great):

Gosper glider gun
Glider Glider
Gosper glider gun

0 komment
Megosztás, like stb.

Alant látható a Raspberry Pi-on futtatott OpenTTD szerver teljesítményadatai (processzor-, és memóriahasználat). Az ábrából kiderül, hogy egész jól bírta a RasPi a terhelést.

A képre kattintva pdf-ben is megtekinthető a diagram, lent pedig a kirajzoló LaTeX fájlt lehet letölteni.

0 komment
Megosztás, like stb.

Using a makefile for large LaTeX projects is a real time saver if you don’t use LaTeX IDEs (e.g. Texmaker, TeXworks). For example some of my friends have to log in to a central server, which provides the same LaTeX setup for everyone in their institution, to compile their documents. In this particular case using make is a must do! thing if you don’t want to fiddle around keeping track of which was the last command you executed or whether your pdf is up to date.

So, what are these makefile and make things?

Make is a tool which controls the generation of executables and other non-source files of a program from the program’s source files.

Make gets its knowledge of how to build your program from a file called the makefile, which lists each of the non-source files and how to compute it from other files. When you write a program, you should write a makefile for it, so that it is possible to use Make to build and install the program.

[…]

Make figures out automatically which files it needs to update, based on which source files have changed. It also automatically determines the proper order for updating files, in case one non-source file depends on another non-source file.

GNU Make

Here is my standard makefile template for projects utilizing pdflatex and bibtex (also downloadable at the end of the post):

1
2
3
4
5
6
7
8
9
10
11
12
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
# Makefile for my LaTeX project

# LaTeX
LC=pdflatex

# Bibtex
BC=bibtex

# The main tex file (without extension)
MAIN=main

# The tex files that are included in the project's main tex file
DEPS=./tex/chapter1.tex ./tex/chapter2.tex ./tex/chapter3.tex

MIDTARGET=$(MAIN).pdf

# The desired filename
TARGET=myprojectv01.pdf

.PHONY: clean show all

all: $(TARGET)

$(TARGET): $(MIDTARGET)
    cp $(MIDTARGET) $(TARGET)

$(MIDTARGET): $(MAIN).tex $(MAIN).aux
    $(BC) $(MAIN).aux
    $(LC) $(MAIN).tex
    $(LC) $(MAIN).tex

$(MAIN).aux: $(MAIN).tex $(DEPS)
    $(LC) $(MAIN).tex

show: $(TARGET)
    xdg-open $< &

clean:
    rm $(MAIN).out $(MAIN).aux $(MAIN).toc $(MAIN).lof $(MAIN).lot $(MAIN).log \
$(MAIN).bbl $(MAIN).blg $(MIDTARGET) $(TARGET)

I won’t go into the details, but if you want to complement this makefile for using latex instead of pdflatex then you’ll have to create another middle target for creating the dvi file. For those who’ll use this makefile in a non-X environment don’t use the show target beacuse xdg-open is meant to be used under X.

I think the GNU Make Manual is detailed enough to understand what I was talking about. But feel free to contact me if you have any questions.

Download: makefile.tar.gz

0 komment
Megosztás, like stb.

Ez azoknak szól, akik – hozzám hasonlóan – sokat szoptak már a hyperref csomaggal.

Szeptemberben volt egy alkalom, amikor fekvő A4-es méretű lappal kellett dolgoznom. Ez eddig nem ügy, amíg később rá nem jöttem, hogy kell nekem hyperref is. Betöltöttem hát. Ekkor borult fel minden, mert az elkészült oldal valami félig fekete, álló A4-es dokumentum lett. Alászálltam a net legmélyebb bugyraiba, ahol megtaláltam a választ a kérdésemre. Először töltsük be a hyperref csomagot a setpagesize=false opcióval és csak aztán a geometry-t.

1
2
3
\documentclass{memoir}
\usepackage[setpagesize=false, xetex]{hyperref}
\usepackage[a4paper, landscape, xetex]{geometry
}

Ma pedig nekiálltam review-olni egy egy évvel korábbi projektemet. Egy darab kommentet nem írtam hozzá, úgyhogy ezzel el is ment a napom nagy része. Aztán ugyanabba a problémába ütköztem, amibe már egy évvel ezelőtt is. Vagyis a kész PDF-ben az összes bookmarkot egyetlen láncba fűzve, nem pedig szép fát találtam. Újfent alászálltam a mélybe, és egy Debian maintenance levlistán találtam meg a megoldást.

“magyar.ldf” redefines most of the essential commands
that also hyperref must change. A patch would have to
include many of hyperref/driver stuff/nameref, adopted
to magyar.ldf.

Alatta pedig ott volt egy gyönyörű makró:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
\makeatletter
\let\Hy@magyar@saved@refstepcounter\refstepcounter
\addto\extrasmagyar{%
  \let\H@refstepcounter\refstepcounter
  \let\refstepcounter\Hy@magyar@saved@refstepcounter
  \expandafter\renewcommand\expandafter*\expandafter\@ssect
      \expandafter[\expandafter 5\expandafter]\expandafter{%
    \expandafter\def\expandafter\@currentlabelname\expandafter{%
      \expandafter #\expandafter 5\expandafter
    }%
    \@ssect{#1}{#2}{#3}{#4}{#5}%
    \phantomsection
  }%
  \expandafter\Hy@magyar@patch@sect\expandafter{%
    \@sect{#1}{#2}{#3}{#4}{#5}{#6}[{#7}]{#8}%
  }{#1#2#3#4#5#6[#7]#8}{#2}{#7}%
}
\def\Hy@magyar@patch@sect#1#2#3#4{%
  \def\@sect#2{%
    \setcounter{section@level}{#3}%
    \def\@currentlabelname{#4}%
    \ifnum #3>\c@secnumdepth
      \Hy@GlobalStepCount\Hy@linkcounter
      \xdef\@currentHref{section*.\the\Hy@linkcounter}%
    \fi
    #1%
    \ifnum #3>\c@secnumdepth
      \Hy@raisedlink{\hyper@anchorstart{\@currentHref}\hyper@anchorend}%
    \fi
  }%
}
\makeatother

Őszintén megmondom, még nem néztem át tüzetesebben, de eddig minden gondomat megoldotta.

0 komment
Megosztás, like stb.

This is my submission for the Show Off Your Skillz in TeX & Friends Contest at TeX.sx.

Full LaTeX source and description coming soon below the picture.

1
2
3
4
5
6
7
8
9
10
11
12
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
% TypoTux
% Created by szantaii
% Compiled with pdflatex using TeX Live 2011
%
% Use it as you like, but please send me an e-mail if you do.

\documentclass[10pt,a4paper]{article}

% Specifying input encoding
\usepackage[utf8]{inputenc}

% Using the TikZ library for drawing Tux, and clipping
\usepackage{tikz}

% Loaded for unnumbered captions
\usepackage{caption}

% For inserting lorem ipsum text
\usepackage{lipsum}

% Setting baselines smaller
\setlength{\baselineskip}{6pt}

\begin{document
}

%\lipsum[1]

\begin{figure}[h!]
\centering
\begin{tikzpicture}[y=0.80pt,x=0.80pt,yscale=-1, inner sep=0pt, outer sep=0pt,scale=0.25]
\begin{scope}[shift={(-249.6684,-294.38903)}
]
%\begin{scope}[cm={{0.55569,0.0,0.0,0.55569,(249.6684,292.4824)}}]
\begin{scope}

% Tux's path used for clipping
% Generated with Inkscape / http://inkscape.org/
% Using the inkscape2tikz extension / http://code.google.com/p/inkscape2tikz/
\path[clip] (265.5000,1063.0000) .. controls (252.9000,1061.0000) and
% Tux's path used for clipping cut from here because it's too long to include.
% See link for full compilable source at the end of the post.
   
% Adding lorem ipsum text using TikZ nodes, and the lipsum package
\node[inner sep=0,text width=10cm] (text1) at (450,450) {\LARGE\textit{\lipsum[1]}};
\node[inner sep=0,text width=10cm] (text1) at (500,500) {\LARGE\textit{\lipsum[2]}};
\node[inner sep=0,text width=10cm] (text1) at (450,450) {\textit{\lipsum[3-4]}};
\node[inner sep=0,text width=10cm] (text1) at (500,500) {\textit{\lipsum[5-6]}};
\node[inner sep=0,text width=10cm] (text2) at (450,450) {\scriptsize\textit{\lipsum[7-11]}};
\node[inner sep=0,text width=10cm] (text2) at (475,475) {\scriptsize\textit{\lipsum[12-16]}};
\node[inner sep=0,text width=10cm] (text2) at (500,500) {\scriptsize\textit{\lipsum[17-21]}};
\end{scope}
\end{scope}
\end{tikzpicture}

\caption*{TypoTux}
\end{figure
}

%\lipsum[2]

\end{document}

I have always wanted to create something like this. After I read about this contest I decided to make a Tux who’s drawn by text. I chose TikZ & PGF for this purpose since it’s very powerful, and I used it several times before for path clipping.

Step-by-step description how I made this:

  1. Grabbed a 2D vectorized Tux image from Wikipedia.
  2. Removed colored and unnecessary white paths from the image, resized page with Inkscape.
  3. Saved the black & white image as a standalone TikZ image with Inkscape using the inkscape2tikz extension.
  4. Removed some unnecessary paths from the tex source file, changed path filling to clipping.
  5. Added nodes filled with different size of lorem ipsum text using the lipsum package. Note: this was the hardest part, because there was no other way but trying to place the text under the clip path.
  6. A little bit of fancying, and cleaning up.

Use it for whatever you want to, but please let me know if you do. I compiled it with pdflatex using TeX Live 2011.

Full compilable source

0 komment
Megosztás, like stb.