Last active
September 30, 2024 10:31
-
-
Save tomgidden/a044434d247fb8626f66d2c056babfe8 to your computer and use it in GitHub Desktop.
Emergency A4 graph paper
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
%PDF-1.4 | |
1 0 obj | |
<< | |
/Type /Catalog | |
/Pages 2 0 R | |
>> | |
endobj | |
2 0 obj | |
<< | |
/Type /Pages | |
/Kids [3 0 R] | |
/Count 1 | |
>> | |
endobj | |
3 0 obj | |
<< | |
/Type /Page | |
/Parent 2 0 R | |
/Resources 4 0 R | |
/MediaBox [0 0 595.2755905511812 841.8897637795277] | |
/Contents 5 0 R | |
>> | |
endobj | |
4 0 obj | |
<< | |
/ProcSet [/PDF] | |
/ColorSpace <</CS1 [/DeviceCMYK]>> | |
>> | |
endobj | |
5 0 obj | |
<< | |
/Length 9246 | |
>> | |
stream | |
/CS1 CS | |
2.834645669291339 0 0 2.834645669291339 0 0 cm | |
1 0 0 1 15 8.5 cm | |
2 J | |
0 j | |
0.5 0 0 0 SC | |
0.05 w | |
0 0 m 0 280 l | |
1 0 m 1 280 l | |
2 0 m 2 280 l | |
3 0 m 3 280 l | |
4 0 m 4 280 l | |
5 0 m 5 280 l | |
6 0 m 6 280 l | |
7 0 m 7 280 l | |
8 0 m 8 280 l | |
9 0 m 9 280 l | |
10 0 m 10 280 l | |
11 0 m 11 280 l | |
12 0 m 12 280 l | |
13 0 m 13 280 l | |
14 0 m 14 280 l | |
15 0 m 15 280 l | |
16 0 m 16 280 l | |
17 0 m 17 280 l | |
18 0 m 18 280 l | |
19 0 m 19 280 l | |
20 0 m 20 280 l | |
21 0 m 21 280 l | |
22 0 m 22 280 l | |
23 0 m 23 280 l | |
24 0 m 24 280 l | |
25 0 m 25 280 l | |
26 0 m 26 280 l | |
27 0 m 27 280 l | |
28 0 m 28 280 l | |
29 0 m 29 280 l | |
30 0 m 30 280 l | |
31 0 m 31 280 l | |
32 0 m 32 280 l | |
33 0 m 33 280 l | |
34 0 m 34 280 l | |
35 0 m 35 280 l | |
36 0 m 36 280 l | |
37 0 m 37 280 l | |
38 0 m 38 280 l | |
39 0 m 39 280 l | |
40 0 m 40 280 l | |
41 0 m 41 280 l | |
42 0 m 42 280 l | |
43 0 m 43 280 l | |
44 0 m 44 280 l | |
45 0 m 45 280 l | |
46 0 m 46 280 l | |
47 0 m 47 280 l | |
48 0 m 48 280 l | |
49 0 m 49 280 l | |
50 0 m 50 280 l | |
51 0 m 51 280 l | |
52 0 m 52 280 l | |
53 0 m 53 280 l | |
54 0 m 54 280 l | |
55 0 m 55 280 l | |
56 0 m 56 280 l | |
57 0 m 57 280 l | |
58 0 m 58 280 l | |
59 0 m 59 280 l | |
60 0 m 60 280 l | |
61 0 m 61 280 l | |
62 0 m 62 280 l | |
63 0 m 63 280 l | |
64 0 m 64 280 l | |
65 0 m 65 280 l | |
66 0 m 66 280 l | |
67 0 m 67 280 l | |
68 0 m 68 280 l | |
69 0 m 69 280 l | |
70 0 m 70 280 l | |
71 0 m 71 280 l | |
72 0 m 72 280 l | |
73 0 m 73 280 l | |
74 0 m 74 280 l | |
75 0 m 75 280 l | |
76 0 m 76 280 l | |
77 0 m 77 280 l | |
78 0 m 78 280 l | |
79 0 m 79 280 l | |
80 0 m 80 280 l | |
81 0 m 81 280 l | |
82 0 m 82 280 l | |
83 0 m 83 280 l | |
84 0 m 84 280 l | |
85 0 m 85 280 l | |
86 0 m 86 280 l | |
87 0 m 87 280 l | |
88 0 m 88 280 l | |
89 0 m 89 280 l | |
90 0 m 90 280 l | |
91 0 m 91 280 l | |
92 0 m 92 280 l | |
93 0 m 93 280 l | |
94 0 m 94 280 l | |
95 0 m 95 280 l | |
96 0 m 96 280 l | |
97 0 m 97 280 l | |
98 0 m 98 280 l | |
99 0 m 99 280 l | |
100 0 m 100 280 l | |
101 0 m 101 280 l | |
102 0 m 102 280 l | |
103 0 m 103 280 l | |
104 0 m 104 280 l | |
105 0 m 105 280 l | |
106 0 m 106 280 l | |
107 0 m 107 280 l | |
108 0 m 108 280 l | |
109 0 m 109 280 l | |
110 0 m 110 280 l | |
111 0 m 111 280 l | |
112 0 m 112 280 l | |
113 0 m 113 280 l | |
114 0 m 114 280 l | |
115 0 m 115 280 l | |
116 0 m 116 280 l | |
117 0 m 117 280 l | |
118 0 m 118 280 l | |
119 0 m 119 280 l | |
120 0 m 120 280 l | |
121 0 m 121 280 l | |
122 0 m 122 280 l | |
123 0 m 123 280 l | |
124 0 m 124 280 l | |
125 0 m 125 280 l | |
126 0 m 126 280 l | |
127 0 m 127 280 l | |
128 0 m 128 280 l | |
129 0 m 129 280 l | |
130 0 m 130 280 l | |
131 0 m 131 280 l | |
132 0 m 132 280 l | |
133 0 m 133 280 l | |
134 0 m 134 280 l | |
135 0 m 135 280 l | |
136 0 m 136 280 l | |
137 0 m 137 280 l | |
138 0 m 138 280 l | |
139 0 m 139 280 l | |
140 0 m 140 280 l | |
141 0 m 141 280 l | |
142 0 m 142 280 l | |
143 0 m 143 280 l | |
144 0 m 144 280 l | |
145 0 m 145 280 l | |
146 0 m 146 280 l | |
147 0 m 147 280 l | |
148 0 m 148 280 l | |
149 0 m 149 280 l | |
150 0 m 150 280 l | |
151 0 m 151 280 l | |
152 0 m 152 280 l | |
153 0 m 153 280 l | |
154 0 m 154 280 l | |
155 0 m 155 280 l | |
156 0 m 156 280 l | |
157 0 m 157 280 l | |
158 0 m 158 280 l | |
159 0 m 159 280 l | |
160 0 m 160 280 l | |
161 0 m 161 280 l | |
162 0 m 162 280 l | |
163 0 m 163 280 l | |
164 0 m 164 280 l | |
165 0 m 165 280 l | |
166 0 m 166 280 l | |
167 0 m 167 280 l | |
168 0 m 168 280 l | |
169 0 m 169 280 l | |
170 0 m 170 280 l | |
171 0 m 171 280 l | |
172 0 m 172 280 l | |
173 0 m 173 280 l | |
174 0 m 174 280 l | |
175 0 m 175 280 l | |
176 0 m 176 280 l | |
177 0 m 177 280 l | |
178 0 m 178 280 l | |
179 0 m 179 280 l | |
180 0 m 180 280 l | |
S | |
0 0 m 180 0 l | |
0 1 m 180 1 l | |
0 2 m 180 2 l | |
0 3 m 180 3 l | |
0 4 m 180 4 l | |
0 5 m 180 5 l | |
0 6 m 180 6 l | |
0 7 m 180 7 l | |
0 8 m 180 8 l | |
0 9 m 180 9 l | |
0 10 m 180 10 l | |
0 11 m 180 11 l | |
0 12 m 180 12 l | |
0 13 m 180 13 l | |
0 14 m 180 14 l | |
0 15 m 180 15 l | |
0 16 m 180 16 l | |
0 17 m 180 17 l | |
0 18 m 180 18 l | |
0 19 m 180 19 l | |
0 20 m 180 20 l | |
0 21 m 180 21 l | |
0 22 m 180 22 l | |
0 23 m 180 23 l | |
0 24 m 180 24 l | |
0 25 m 180 25 l | |
0 26 m 180 26 l | |
0 27 m 180 27 l | |
0 28 m 180 28 l | |
0 29 m 180 29 l | |
0 30 m 180 30 l | |
0 31 m 180 31 l | |
0 32 m 180 32 l | |
0 33 m 180 33 l | |
0 34 m 180 34 l | |
0 35 m 180 35 l | |
0 36 m 180 36 l | |
0 37 m 180 37 l | |
0 38 m 180 38 l | |
0 39 m 180 39 l | |
0 40 m 180 40 l | |
0 41 m 180 41 l | |
0 42 m 180 42 l | |
0 43 m 180 43 l | |
0 44 m 180 44 l | |
0 45 m 180 45 l | |
0 46 m 180 46 l | |
0 47 m 180 47 l | |
0 48 m 180 48 l | |
0 49 m 180 49 l | |
0 50 m 180 50 l | |
0 51 m 180 51 l | |
0 52 m 180 52 l | |
0 53 m 180 53 l | |
0 54 m 180 54 l | |
0 55 m 180 55 l | |
0 56 m 180 56 l | |
0 57 m 180 57 l | |
0 58 m 180 58 l | |
0 59 m 180 59 l | |
0 60 m 180 60 l | |
0 61 m 180 61 l | |
0 62 m 180 62 l | |
0 63 m 180 63 l | |
0 64 m 180 64 l | |
0 65 m 180 65 l | |
0 66 m 180 66 l | |
0 67 m 180 67 l | |
0 68 m 180 68 l | |
0 69 m 180 69 l | |
0 70 m 180 70 l | |
0 71 m 180 71 l | |
0 72 m 180 72 l | |
0 73 m 180 73 l | |
0 74 m 180 74 l | |
0 75 m 180 75 l | |
0 76 m 180 76 l | |
0 77 m 180 77 l | |
0 78 m 180 78 l | |
0 79 m 180 79 l | |
0 80 m 180 80 l | |
0 81 m 180 81 l | |
0 82 m 180 82 l | |
0 83 m 180 83 l | |
0 84 m 180 84 l | |
0 85 m 180 85 l | |
0 86 m 180 86 l | |
0 87 m 180 87 l | |
0 88 m 180 88 l | |
0 89 m 180 89 l | |
0 90 m 180 90 l | |
0 91 m 180 91 l | |
0 92 m 180 92 l | |
0 93 m 180 93 l | |
0 94 m 180 94 l | |
0 95 m 180 95 l | |
0 96 m 180 96 l | |
0 97 m 180 97 l | |
0 98 m 180 98 l | |
0 99 m 180 99 l | |
0 100 m 180 100 l | |
0 101 m 180 101 l | |
0 102 m 180 102 l | |
0 103 m 180 103 l | |
0 104 m 180 104 l | |
0 105 m 180 105 l | |
0 106 m 180 106 l | |
0 107 m 180 107 l | |
0 108 m 180 108 l | |
0 109 m 180 109 l | |
0 110 m 180 110 l | |
0 111 m 180 111 l | |
0 112 m 180 112 l | |
0 113 m 180 113 l | |
0 114 m 180 114 l | |
0 115 m 180 115 l | |
0 116 m 180 116 l | |
0 117 m 180 117 l | |
0 118 m 180 118 l | |
0 119 m 180 119 l | |
0 120 m 180 120 l | |
0 121 m 180 121 l | |
0 122 m 180 122 l | |
0 123 m 180 123 l | |
0 124 m 180 124 l | |
0 125 m 180 125 l | |
0 126 m 180 126 l | |
0 127 m 180 127 l | |
0 128 m 180 128 l | |
0 129 m 180 129 l | |
0 130 m 180 130 l | |
0 131 m 180 131 l | |
0 132 m 180 132 l | |
0 133 m 180 133 l | |
0 134 m 180 134 l | |
0 135 m 180 135 l | |
0 136 m 180 136 l | |
0 137 m 180 137 l | |
0 138 m 180 138 l | |
0 139 m 180 139 l | |
0 140 m 180 140 l | |
0 141 m 180 141 l | |
0 142 m 180 142 l | |
0 143 m 180 143 l | |
0 144 m 180 144 l | |
0 145 m 180 145 l | |
0 146 m 180 146 l | |
0 147 m 180 147 l | |
0 148 m 180 148 l | |
0 149 m 180 149 l | |
0 150 m 180 150 l | |
0 151 m 180 151 l | |
0 152 m 180 152 l | |
0 153 m 180 153 l | |
0 154 m 180 154 l | |
0 155 m 180 155 l | |
0 156 m 180 156 l | |
0 157 m 180 157 l | |
0 158 m 180 158 l | |
0 159 m 180 159 l | |
0 160 m 180 160 l | |
0 161 m 180 161 l | |
0 162 m 180 162 l | |
0 163 m 180 163 l | |
0 164 m 180 164 l | |
0 165 m 180 165 l | |
0 166 m 180 166 l | |
0 167 m 180 167 l | |
0 168 m 180 168 l | |
0 169 m 180 169 l | |
0 170 m 180 170 l | |
0 171 m 180 171 l | |
0 172 m 180 172 l | |
0 173 m 180 173 l | |
0 174 m 180 174 l | |
0 175 m 180 175 l | |
0 176 m 180 176 l | |
0 177 m 180 177 l | |
0 178 m 180 178 l | |
0 179 m 180 179 l | |
0 180 m 180 180 l | |
0 181 m 180 181 l | |
0 182 m 180 182 l | |
0 183 m 180 183 l | |
0 184 m 180 184 l | |
0 185 m 180 185 l | |
0 186 m 180 186 l | |
0 187 m 180 187 l | |
0 188 m 180 188 l | |
0 189 m 180 189 l | |
0 190 m 180 190 l | |
0 191 m 180 191 l | |
0 192 m 180 192 l | |
0 193 m 180 193 l | |
0 194 m 180 194 l | |
0 195 m 180 195 l | |
0 196 m 180 196 l | |
0 197 m 180 197 l | |
0 198 m 180 198 l | |
0 199 m 180 199 l | |
0 200 m 180 200 l | |
0 201 m 180 201 l | |
0 202 m 180 202 l | |
0 203 m 180 203 l | |
0 204 m 180 204 l | |
0 205 m 180 205 l | |
0 206 m 180 206 l | |
0 207 m 180 207 l | |
0 208 m 180 208 l | |
0 209 m 180 209 l | |
0 210 m 180 210 l | |
0 211 m 180 211 l | |
0 212 m 180 212 l | |
0 213 m 180 213 l | |
0 214 m 180 214 l | |
0 215 m 180 215 l | |
0 216 m 180 216 l | |
0 217 m 180 217 l | |
0 218 m 180 218 l | |
0 219 m 180 219 l | |
0 220 m 180 220 l | |
0 221 m 180 221 l | |
0 222 m 180 222 l | |
0 223 m 180 223 l | |
0 224 m 180 224 l | |
0 225 m 180 225 l | |
0 226 m 180 226 l | |
0 227 m 180 227 l | |
0 228 m 180 228 l | |
0 229 m 180 229 l | |
0 230 m 180 230 l | |
0 231 m 180 231 l | |
0 232 m 180 232 l | |
0 233 m 180 233 l | |
0 234 m 180 234 l | |
0 235 m 180 235 l | |
0 236 m 180 236 l | |
0 237 m 180 237 l | |
0 238 m 180 238 l | |
0 239 m 180 239 l | |
0 240 m 180 240 l | |
0 241 m 180 241 l | |
0 242 m 180 242 l | |
0 243 m 180 243 l | |
0 244 m 180 244 l | |
0 245 m 180 245 l | |
0 246 m 180 246 l | |
0 247 m 180 247 l | |
0 248 m 180 248 l | |
0 249 m 180 249 l | |
0 250 m 180 250 l | |
0 251 m 180 251 l | |
0 252 m 180 252 l | |
0 253 m 180 253 l | |
0 254 m 180 254 l | |
0 255 m 180 255 l | |
0 256 m 180 256 l | |
0 257 m 180 257 l | |
0 258 m 180 258 l | |
0 259 m 180 259 l | |
0 260 m 180 260 l | |
0 261 m 180 261 l | |
0 262 m 180 262 l | |
0 263 m 180 263 l | |
0 264 m 180 264 l | |
0 265 m 180 265 l | |
0 266 m 180 266 l | |
0 267 m 180 267 l | |
0 268 m 180 268 l | |
0 269 m 180 269 l | |
0 270 m 180 270 l | |
0 271 m 180 271 l | |
0 272 m 180 272 l | |
0 273 m 180 273 l | |
0 274 m 180 274 l | |
0 275 m 180 275 l | |
0 276 m 180 276 l | |
0 277 m 180 277 l | |
0 278 m 180 278 l | |
0 279 m 180 279 l | |
0 280 m 180 280 l | |
S | |
0.2 w | |
0 0 m 0 280 l | |
10 0 m 10 280 l | |
20 0 m 20 280 l | |
30 0 m 30 280 l | |
40 0 m 40 280 l | |
50 0 m 50 280 l | |
60 0 m 60 280 l | |
70 0 m 70 280 l | |
80 0 m 80 280 l | |
90 0 m 90 280 l | |
100 0 m 100 280 l | |
110 0 m 110 280 l | |
120 0 m 120 280 l | |
130 0 m 130 280 l | |
140 0 m 140 280 l | |
150 0 m 150 280 l | |
160 0 m 160 280 l | |
170 0 m 170 280 l | |
180 0 m 180 280 l | |
S | |
0 0 m 180 0 l | |
0 10 m 180 10 l | |
0 20 m 180 20 l | |
0 30 m 180 30 l | |
0 40 m 180 40 l | |
0 50 m 180 50 l | |
0 60 m 180 60 l | |
0 70 m 180 70 l | |
0 80 m 180 80 l | |
0 90 m 180 90 l | |
0 100 m 180 100 l | |
0 110 m 180 110 l | |
0 120 m 180 120 l | |
0 130 m 180 130 l | |
0 140 m 180 140 l | |
0 150 m 180 150 l | |
0 160 m 180 160 l | |
0 170 m 180 170 l | |
0 180 m 180 180 l | |
0 190 m 180 190 l | |
0 200 m 180 200 l | |
0 210 m 180 210 l | |
0 220 m 180 220 l | |
0 230 m 180 230 l | |
0 240 m 180 240 l | |
0 250 m 180 250 l | |
0 260 m 180 260 l | |
0 270 m 180 270 l | |
0 280 m 180 280 l | |
S | |
0.4 w | |
0 0 m 0 280 l | |
20 0 m 20 280 l | |
40 0 m 40 280 l | |
60 0 m 60 280 l | |
80 0 m 80 280 l | |
100 0 m 100 280 l | |
120 0 m 120 280 l | |
140 0 m 140 280 l | |
160 0 m 160 280 l | |
180 0 m 180 280 l | |
S | |
0 0 m 180 0 l | |
0 20 m 180 20 l | |
0 40 m 180 40 l | |
0 60 m 180 60 l | |
0 80 m 180 80 l | |
0 100 m 180 100 l | |
0 120 m 180 120 l | |
0 140 m 180 140 l | |
0 160 m 180 160 l | |
0 180 m 180 180 l | |
0 200 m 180 200 l | |
0 220 m 180 220 l | |
0 240 m 180 240 l | |
0 260 m 180 260 l | |
0 280 m 180 280 l | |
S | |
endstream | |
endobj | |
xref | |
0 6 | |
0000000000 65535 f | |
0000000009 00000 n | |
0000000062 00000 n | |
0000000125 00000 n | |
0000000267 00000 n | |
0000000343 00000 n | |
trailer | |
<< | |
/Size 6 | |
/Root 1 0 R | |
>> | |
startxref | |
9643 | |
%%EOF |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- ghc generate_graph_paper.hs | |
-- Install ghcup first! | |
-- Tom Gidden <[email protected]> | |
module Main where | |
import Data.List (intercalate) | |
import System.IO () | |
import Text.Printf (printf) | |
-- 'intercalate "\n" [...]' is like 'buf.join("\n")' in other languages | |
joinLines :: [[Char]] -> [Char] | |
joinLines = intercalate "\n" | |
pdfHeader :: String | |
pdfHeader = "%PDF-1.4" | |
pdfFooter :: String | |
pdfFooter = "%%EOF" | |
-- Conversion factor (points per mm) | |
pointsPerMM :: Double | |
pointsPerMM = 72 / 25.4 | |
-- A4 Page dimensions in millimetres | |
pageWidth, pageHeight :: Double | |
pageWidth = 210 | |
pageHeight = 297 | |
-- Minimum page margin in millimetres | |
margin :: Double | |
margin = 8 | |
-- Number of millimetres per line (for small, medium and large divisions) | |
-- so if smallGridMM = 1, this is a small division every millimetre; mediumGridMM = 10 | |
-- is a medium division every 10 millimetres. | |
smallGridMM, mediumGridMM, largeGridMM :: Double | |
smallGridMM = 1 | |
mediumGridMM = 10 | |
largeGridMM = 20 | |
-- Line widths for each grid (in millimetres) | |
smallLineWidth, mediumLineWidth, largeLineWidth :: Double | |
smallLineWidth = 0.05 | |
mediumLineWidth = 0.2 | |
largeLineWidth = 0.4 | |
-- Calculate number of complete large squares possible | |
numSquaresX, numSquaresY :: Int | |
numSquaresX = floor ((pageWidth - 2 * margin) / largeGridMM) | |
numSquaresY = floor ((pageHeight - 2 * margin) / largeGridMM) | |
-- Resulting grid dimensions (in mm) | |
gridWidth, gridHeight :: Double | |
gridWidth = largeGridMM * fromIntegral numSquaresX | |
gridHeight = largeGridMM * fromIntegral numSquaresY | |
-- Calculate translation of bottom left to centre the grid on the page | |
x0, y0 :: Double | |
x0 = (pageWidth - gridWidth) / 2 | |
y0 = (pageHeight - gridHeight) / 2 | |
-- The transformation of scaling of points to mm, and then shifting the | |
-- origin to the bottom-left of the grid to be drawn | |
matrix :: String | |
matrix = printf "%f 0 0 %f %f %f cm" pointsPerMM pointsPerMM (x0 * pointsPerMM) (y0 * pointsPerMM) | |
-- The actual graphics on the page | |
stream :: String | |
stream = | |
joinLines | |
[ matrix, | |
"/CS1 CS", -- CMYK mode | |
"2 J", -- Line caps square | |
"0 j", -- Mitre join | |
"0.5 0 0 0 SC", -- Set stroke color to cyan in CMYK | |
drawGrid -- Draw the grid | |
] | |
-- Draw the grid for small, medium and large divisions | |
drawGrid :: String | |
drawGrid = | |
joinLines | |
[ drawLines 0 gridWidth 0 gridHeight smallGridMM smallLineWidth, | |
drawLines 0 gridWidth 0 gridHeight mediumGridMM mediumLineWidth, | |
drawLines 0 gridWidth 0 gridHeight largeGridMM largeLineWidth | |
] | |
-- Draw a grid of lines: each grid is a series of horizontal lines as one stroke, | |
-- and a series of vertical lines. | |
drawLines :: Double -> Double -> Double -> Double -> Double -> Double -> String | |
drawLines startX endX startY endY step lineWidth = | |
joinLines | |
[ printf "%f w" lineWidth, -- Set line width | |
unlines [drawLine x startY x endY | x <- [startX, startX + step .. endX]], -- Vertical lines | |
unlines [drawLine startX y endX y | y <- [startY, startY + step .. endY]], -- Horizontal lines | |
"S" -- Stroke the path | |
] | |
-- Draw a single line | |
drawLine :: Double -> Double -> Double -> Double -> String | |
drawLine = printf "%f %f m %f %f l" | |
-- PDF objects that make up our "document" | |
pdfObjects :: [String] | |
pdfObjects = | |
[ joinLines | |
[ "1 0 obj", | |
"<<", | |
" /Type /Catalog", | |
" /Pages 2 0 R", | |
">>", | |
"endobj" | |
], | |
joinLines | |
[ "2 0 obj", | |
"<<", | |
" /Type /Pages", | |
" /Kids [3 0 R]", | |
" /Count 1", | |
">>", | |
"endobj" | |
], | |
joinLines | |
[ "3 0 obj", | |
"<<", | |
" /Type /Page", | |
" /Parent 2 0 R", | |
" /Resources 4 0 R", | |
printf " /MediaBox [0 0 %f %f]" (pageWidth * pointsPerMM) (pageHeight * pointsPerMM), | |
" /Contents 5 0 R", | |
">>", | |
"endobj" | |
], | |
joinLines | |
[ "4 0 obj", | |
"<<", | |
" /ProcSet [/PDF]", | |
" /ColorSpace <</CS1 [/DeviceCMYK]>> ", | |
">>", | |
"endobj" | |
], | |
joinLines | |
[ "5 0 obj", | |
"<<", | |
printf " /Length %d" (length stream), | |
">>", | |
"stream", | |
stream, | |
"endstream", | |
"endobj" | |
] | |
] | |
-- For each string, work out the cumulative start position. | |
-- `scanl fn initialValue objects` is like `objects.reduce(fn, initialValue)` | |
findOffsets :: Int -> [String] -> [Int] | |
findOffsets = scanl (\acc obj -> acc + length obj + 1) | |
-- Build the xref table, which gives the byte-offsets in the file for each | |
-- PDF object. | |
xref :: String | |
xref = | |
joinLines | |
[ "xref", | |
printf "0 %d" (length pdfObjects + 1), | |
"0000000000 65535 f", | |
xrefTable | |
] | |
where | |
offsets = init $ findOffsets (length pdfHeader + 1) pdfObjects | |
makeXrefEntry (i, offset) = printf "%010d 00000 n" offset | |
xrefTable = joinLines xrefs | |
xrefs = map makeXrefEntry (zip [1 ..] offsets) | |
-- Everything up to the xref table | |
startOfPdf :: String | |
startOfPdf = | |
joinLines | |
[ pdfHeader, | |
joinLines pdfObjects | |
] | |
-- Trailer, which indicates the number of objects, the root of the document | |
-- and the location of the xref table. | |
trailer :: String | |
trailer = | |
joinLines | |
[ "trailer", | |
"<<", | |
printf " /Size %d" (length pdfObjects + 1), | |
" /Root 1 0 R", | |
">>", | |
"startxref", | |
show (1 + length startOfPdf) | |
] | |
-- Build the entire PDF buffer. | |
pdf :: String | |
pdf = | |
joinLines | |
[ startOfPdf, | |
xref, | |
trailer, | |
pdfFooter | |
] | |
-- Output the PDF buffer to stdout. | |
main :: IO () | |
main = putStr pdf |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!python | |
# by Tom Gidden <[email protected]>, 2024 | |
import math | |
# We'll do everything in millimetres | |
# Conversion factor (points per mm) | |
pointsPerMM = 72 / 25.4 | |
# A4 Page dimensions in millimetres | |
pageWidth = 210 | |
pageHeight = 297 | |
# Minimum page margin in millimetres | |
margin = 8 | |
# Cyan in CMYK (0-1 scale) | |
color_cmyk = [0.5, 0, 0, 0] | |
# Number of millimetres per line (for small, medium and large divisions) | |
line_widths = { | |
'S': 0.05, | |
'M': 0.2, | |
'L': 0.4 | |
} | |
# Line widths for each grid (in millimetres) | |
line_spacing = { | |
'S': 1, | |
'M': 10, | |
'L': 20 | |
} | |
# Round to nearest large division and add margins. | |
gridWidth = math.floor((pageWidth - 2 * margin) / line_spacing['L']) * line_spacing['L'] | |
gridHeight = math.floor((pageHeight - 2 * margin) / line_spacing['L']) * line_spacing['L'] | |
# Calculate translation of bottom left to centre the grid on the page | |
x0 = (pageWidth - gridWidth) / 2 | |
y0 = (pageHeight - gridHeight) / 2 | |
# PDF content | |
objects = [] | |
# File header | |
objects.append("%PDF-1.4") | |
# Object 1: Catalog | |
objects.append(""" | |
1 0 obj | |
<< | |
/Type /Catalog | |
/Pages 2 0 R | |
>> | |
endobj""") | |
# Object 2: Pages | |
objects.append(""" | |
2 0 obj | |
<< | |
/Type /Pages | |
/Kids [3 0 R] | |
/Count 1 | |
>> | |
endobj""") | |
# Object 3: Page | |
objects.append(f""" | |
3 0 obj | |
<< | |
/Type /Page | |
/Parent 2 0 R | |
/Resources 4 0 R | |
/MediaBox [0 0 {pageWidth * pointsPerMM} {pageHeight * pointsPerMM}] | |
/Contents 5 0 R | |
>> | |
endobj""") | |
# Object 4: Resources; define CS1 to switch to CMYK | |
objects.append(""" | |
4 0 obj | |
<< | |
/ProcSet [/PDF] | |
/ColorSpace <</CS1 [/DeviceCMYK]>> | |
>> | |
endobj""") | |
# Object 5: Contents | |
def vertical_lines(p0, p1, dp): | |
p = p0 | |
while p <= p1: | |
yield f"{p} 0 m {p} {gridHeight} l" | |
p += dp | |
def horizontal_lines(p0, p1, dp): | |
p = p0 | |
while p <= p1: | |
yield f"0 {p} m {gridWidth} {p} l" | |
p += dp | |
def lines(dp): | |
return "\n".join(list(vertical_lines(0, gridWidth, dp)) + list(horizontal_lines(0, gridHeight, dp))) | |
# The graphics command stream for the page content object | |
stream = f""" | |
/CS1 CS | |
{pointsPerMM} 0 0 {pointsPerMM} 0 0 cm | |
1 0 0 1 {x0} {y0} cm | |
2 J | |
0 j | |
{color_cmyk[0]} {color_cmyk[1]} {color_cmyk[2]} {color_cmyk[3]} SC | |
{line_widths['S']} w | |
{lines(line_spacing['S'])} | |
S | |
{line_widths['M']} w | |
{lines(line_spacing['M'])} | |
S | |
{line_widths['L']} w | |
{lines(line_spacing['L'])} | |
S | |
""" | |
# Object 5: the actual page content! | |
objects.append(f""" | |
5 0 obj | |
<</Length {len(stream)} >> | |
stream | |
{stream} | |
endstream | |
endobj""") | |
# Generate xref table | |
xref = [] | |
buf = "" | |
for object in objects: | |
# Add object to page buffer | |
buf += object | |
# Add object to xref table, with start position (len(buf) + 1) | |
xref.append(f"{str(len(buf) + 1).zfill(10)} 00000 n") | |
# We scrap the last entry because it's the xref itself. | |
xref.pop() | |
# Build the pdf | |
pdf = f"""{buf} | |
xref | |
0 {len(objects)} | |
0000000000 65535 f | |
{chr(10).join(xref)} | |
trailer | |
<< | |
/Size {len(objects)} | |
/Root 1 0 R | |
>> | |
startxref | |
{len(buf) + 1} | |
%%EOF""" | |
# Output | |
print(pdf) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bun run -- | |
// by Tom Gidden <[email protected]>, 2024 | |
const pdfHeader = '%PDF-1.4'; | |
const pdfFooter = '%%EOF'; | |
// We'll do everything in millimetres. | |
// Conversion factor (points per mm) | |
const pointsPerMM = 72 / 25.4; | |
// A4 Page dimensions in millimetres | |
const pageWidth = 210; | |
const pageHeight = 297; | |
// Minimum page margin in millimetres | |
const margin = 8; | |
// Cyan in CMYK (0-1 scale) | |
const color_cmyk = [0.5, 0, 0, 0] | |
// Number of millimetres per line (for small, medium and large divisions) | |
const smallGridMM = 1; | |
const mediumGridMM = 10; | |
const largeGridMM = 20; | |
// Line widths for each grid (in millimetres) | |
const smallLineWidth = 0.05; | |
const mediumLineWidth = 0.2; | |
const largeLineWidth = 0.4; | |
// Decimal places for grid lines. Should not be necessary, but | |
// rounding errors make for big files. | |
const precision = 0; | |
// Calculate number of complete large squares possible | |
const numSquaresX = Math.floor((pageWidth - 2 * margin) / largeGridMM); | |
const numSquaresY = Math.floor((pageHeight - 2 * margin) / largeGridMM); | |
// Resulting grid dimensions (in mm) | |
const gridWidth = largeGridMM * numSquaresX; | |
const gridHeight = largeGridMM * numSquaresY; | |
// Calculate translation of bottom left to centre the grid on the page | |
const x0 = (pageWidth - gridWidth) / 2; | |
const y0 = (pageHeight - gridHeight) / 2; | |
// CS: change to CMYK mode | |
// cm (1): set scaling factor to millimetres by changing current transformation matrix | |
// cm (2): move origin to x0,y0 by changing current transformation matrix | |
// J: set to square end caps | |
// j: set to mitre joins | |
// SC: set stroke colour | |
// w: set line width | |
// S: stroke path | |
// m: move-to | |
// l: line-to | |
const drawLine = (x1: number, y1: number, x2: number, y2: number): string => | |
`${x1.toFixed(precision)} ${y1.toFixed(precision)} m ${x2.toFixed(precision)} ${y2.toFixed(precision)} l`; | |
// Draw a grid of lines | |
function* verticalLines(p0, p1, dp) { | |
for (let p = p0; p <= p1; p += dp) { | |
yield drawLine(p, 0, p, gridHeight); | |
} | |
} | |
function* horizontalLines(p0, p1, dp) { | |
for (let p = p0; p <= p1; p += dp) { | |
yield drawLine(0, p, gridWidth, p); | |
} | |
} | |
function drawLines(x0: number, x1: number, y0: number, y1: number, step: number, lineWidth: number): string { | |
return [ | |
`${lineWidth} w`, | |
...verticalLines(x0, x1, step), | |
`S`, | |
...horizontalLines(y0, y1, step), | |
`S` | |
].join("\n"); | |
} | |
// Draw the grid for small, medium and large divisions | |
function drawGrid(): string { | |
return [ | |
drawLines(0, gridWidth, 0, gridHeight, smallGridMM, smallLineWidth), | |
drawLines(0, gridWidth, 0, gridHeight, mediumGridMM, mediumLineWidth), | |
drawLines(0, gridWidth, 0, gridHeight, largeGridMM, largeLineWidth) | |
].join('\n'); | |
} | |
// The actual graphics on the page | |
const stream: string = `/CS1 CS | |
${pointsPerMM} 0 0 ${pointsPerMM} 0 0 cm | |
1 0 0 1 ${x0} ${y0} cm | |
2 J | |
0 j | |
${color_cmyk.join(" ")} SC | |
${drawGrid()}`; | |
const pdfObjects: string[] = [ | |
`1 0 obj | |
<< | |
/Type /Catalog | |
/Pages 2 0 R | |
>> | |
endobj`, | |
`2 0 obj | |
<< | |
/Type /Pages | |
/Kids [3 0 R] | |
/Count 1 | |
>> | |
endobj`, | |
`3 0 obj | |
<< | |
/Type /Page | |
/Parent 2 0 R | |
/Resources 4 0 R | |
/MediaBox [0 0 ${pageWidth * pointsPerMM} ${pageHeight * pointsPerMM}] | |
/Contents 5 0 R | |
>> | |
endobj`, | |
`4 0 obj | |
<< | |
/ProcSet [/PDF] | |
/ColorSpace <</CS1 [/DeviceCMYK]>> | |
>> | |
endobj`, | |
`5 0 obj | |
<< | |
/Length ${stream.length} | |
>> | |
stream | |
${stream} | |
endstream | |
endobj` | |
]; | |
// Generate the 'xref' table, cross-referencing byte-offsets in the | |
// file to PDF objects. | |
function generateXref(pdfObjects: string[]): string { | |
let xref = [ | |
`xref | |
0 ${pdfObjects.length + 1} | |
0000000000 65535 f` | |
]; | |
let offset = pdfHeader.length + 1; | |
for (let i = 0; i < pdfObjects.length; i++) { | |
xref.push(`${offset.toString().padStart(10, '0')} 00000 n`); | |
offset += pdfObjects[i].length + 1; | |
} | |
return xref.join("\n"); | |
} | |
function generatePDF(): string { | |
const startOfPdf = `${pdfHeader} | |
${pdfObjects.join("\n")}`; | |
const xref = generateXref(pdfObjects); | |
return `${startOfPdf} | |
${xref} | |
trailer | |
<< | |
/Size ${pdfObjects.length + 1} | |
/Root 1 0 R | |
>> | |
startxref | |
${startOfPdf.length + 1} | |
${pdfFooter}`; | |
} | |
// Generate and save the PDF | |
const pdf = generatePDF(); | |
console.log(pdf); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment