tikzify 0.0.36 → 0.0.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tikzify",
3
- "version": "0.0.36",
3
+ "version": "0.0.38",
4
4
  "type": "module",
5
5
  "exports": "./src/index.ts",
6
6
  "files": [
@@ -19,6 +19,7 @@
19
19
  "@types/sax": "^1.2.7",
20
20
  "p-queue": "^9.1.0",
21
21
  "sax": "^1.4.4",
22
+ "sharp": "^0.34.5",
22
23
  "zod": "^4.3.5"
23
24
  }
24
25
  }
package/src/book.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import assert from 'assert'
2
2
  import z from 'zod'
3
- import { color, colorMap, defineColors, Emoji, fromSvg, getColors, gradient, Lang, svgTex, type Transform } from './common'
3
+ import { color, colorMap, defineColors, Emoji, fromSvg, getColors, gradient, Lang, svg, } from './common'
4
4
  import { emojiMap } from './emojis'
5
5
 
6
6
 
@@ -10,7 +10,7 @@ export const Page = z.object({
10
10
  textBackground: z.string(),
11
11
  text: z.string().max(1024),
12
12
  emojis: z.array(Emoji).length(6),
13
- jpgBase64: z.string().max(500_000)
13
+ avifBase64: z.string().max(500_000)
14
14
  })
15
15
 
16
16
  export type Book = z.infer<typeof Book>
@@ -21,12 +21,19 @@ export const Book = z.object({
21
21
  color: color,
22
22
  title: z.string().max(256),
23
23
  author: z.string().max(256),
24
- heroJpgBase64: z.string().max(300_000),
24
+ heroAvifBase64: z.string().max(300_000),
25
25
 
26
26
  // OTHER PAGES
27
27
  pages: z.array(Page),
28
28
  })
29
29
 
30
+ interface Transform {
31
+ x: number
32
+ y: number
33
+ scale: number
34
+ rotate: number
35
+ }
36
+
30
37
  const TRANSFORMS_TEXT: Transform[] = [
31
38
  { x: 10, y: 15, scale: 2.3, rotate: -15, },
32
39
  { x: 300, y: 220, scale: 2.1, rotate: 15, },
@@ -39,6 +46,8 @@ const TRANSFORMS_IMAGE: Transform[] = [
39
46
  { x: 100, y: 80, scale: 2.3, rotate: 10 }
40
47
  ]
41
48
 
49
+ const LINE_SPACING = 1.15
50
+
42
51
  export function bookTex(book: Book) {
43
52
  const pages = book.pages
44
53
 
@@ -73,12 +82,16 @@ export function bookTex(book: Book) {
73
82
  }
74
83
 
75
84
  \usepackage{polyglossia}
76
- \setmainlanguage{hebrew}
85
+ ${book.lang === 'he' ? String.raw`\setmainlanguage{hebrew}
77
86
  \newfontfamily\hebrewfont[
78
87
  Script=Hebrew,
79
88
  Path=./,
80
89
  BoldFont={Fredoka-Bold.ttf}
81
- ]{Fredoka-Bold.ttf}
90
+ ]{Fredoka-Bold.ttf}` : String.raw`\setmainlanguage{english}
91
+ \newfontfamily\englishfont[
92
+ Path=./,
93
+ BoldFont={Fredoka-Bold.ttf}
94
+ ]{Fredoka-Bold.ttf}`}
82
95
 
83
96
 
84
97
  ${defineColors(colors)}
@@ -116,7 +129,11 @@ ${book.pages.map((page, i) => {
116
129
  assert(emojis.length <= transforms.length)
117
130
  return emojis.map((emoji, i) => {
118
131
  const els = emojiElements[emoji]!
119
- return svgTex(transforms[i]!, els, colors)
132
+ return svg({
133
+ ...transforms[i]!,
134
+ elements: els,
135
+ colors
136
+ })
120
137
  }).join('\n')
121
138
  }
122
139
 
@@ -165,7 +182,9 @@ ${esText}
165
182
  \begin{center}
166
183
  \begin{minipage}{10cm}
167
184
  \Huge
168
- \raggedleft
185
+ \linespread{${LINE_SPACING}}\selectfont
186
+ \setlength{\parskip}{0.8em}
187
+ ${rtl ? '\\raggedleft' : '\\raggedright'}
169
188
  ${page.text}
170
189
  \end{minipage}
171
190
  \end{center}
package/src/common.ts CHANGED
@@ -10,8 +10,29 @@ export type Emoji = z.infer<typeof Emoji>
10
10
  export const Emoji = z.object(emojiMap).keyof()
11
11
 
12
12
  const langs = ['he', 'en'] as const
13
+ export type Lang = typeof langs[number]
13
14
  export const Lang = z.enum(langs)
14
15
 
16
+ type LangConfig = {
17
+ name: string
18
+ script: string
19
+ }
20
+
21
+ const langConfig: { [key in Lang]: LangConfig } = {
22
+ he: { name: 'hebrew', script: 'Hebrew' },
23
+ en: { name: 'english', script: 'Latin' },
24
+ }
25
+
26
+ export const langName: { [key in Lang]: string } = {
27
+ he: langConfig.he.name,
28
+ en: langConfig.en.name,
29
+ }
30
+
31
+ export const script: { [key in Lang]: string } = {
32
+ he: langConfig.he.script,
33
+ en: langConfig.en.script,
34
+ }
35
+
15
36
  type Path = z.infer<typeof Path>
16
37
  const Path = z.object({
17
38
  type: z.literal("path"),
@@ -178,16 +199,220 @@ export function defineColors(colors: Record<string, number>) {
178
199
  return Object.entries(colors).map(([color, i]) => `\\definecolor{c${i}}{HTML}{${color.replace('#', '')}}`).join('\n')
179
200
  }
180
201
 
181
- export interface Transform {
202
+ interface Svg {
182
203
  x: number
183
204
  y: number
184
205
  scale: number
185
206
  rotate: number
207
+ elements: Element[]
208
+ colors: Record<string, number>
186
209
  }
187
-
188
- export function svgTex({ x, y, scale, rotate }: Transform, es: Element[], colors: Record<string, number>) {
210
+ export function svg({ x, y, scale, rotate, elements, colors }: Svg) {
189
211
  return String.raw`
190
212
  \begin{scope}[x=1pt, y=1pt, xshift=${x}, scale=${scale}, yscale=-1, yshift=${y}, rotate=${rotate}]
191
- ${es.map(e => toTikz[e.type](e as any, colors)).join('\n')}
213
+ ${elements.map(e => toTikz[e.type](e as any, colors)).join('\n')}
214
+ \end{scope}`
215
+ }
216
+
217
+ export function poly(lang: Lang) {
218
+ const { name, script } = langConfig[lang]
219
+ return String.raw`
220
+ \usepackage{polyglossia}
221
+ \setmainlanguage{${name}}
222
+ \setmainfont{Fredoka}[
223
+ Script=${script},
224
+ Path=./,
225
+ Extension=.ttf,
226
+ UprightFont=*-Regular,
227
+ BoldFont=*-Bold]`
228
+ }
229
+
230
+ type MiniPage = z.infer<typeof MiniPage>
231
+ const MiniPage = z.object({
232
+ vAlign: z.enum(['t', 'c', 'b']),
233
+ width: z.number().min(0).max(1),
234
+ height: z.number().min(0).max(1),
235
+ content: z.string(),
236
+ })
237
+ export function minipage(raw: MiniPage) {
238
+ const { vAlign, width, height, content } = MiniPage.parse(raw)
239
+ return String.raw`\fbox{%
240
+ \begin{minipage}[c][${height}\textheight][${vAlign}]{${width}\textwidth}
241
+ ${content}
242
+ \end{minipage}}%`
243
+ }
244
+
245
+ type Background = z.infer<typeof Background>
246
+ const Background = z.object({
247
+ gradient: gradient,
248
+ colors: z.record(z.string(), z.number()),
249
+ content: z.string(),
250
+ rtl: z.boolean()
251
+ })
252
+
253
+ export function background(raw: Background) {
254
+ const { gradient: [c1, c2], content, colors, rtl } = Background.parse(raw)
255
+ assert(c1 && c2, "Gradient must have two colors")
256
+ const g1 = colors[c1]
257
+ const g2 = colors[c2]
258
+ return String.raw`\begin{tikzpicture}[remember picture, overlay, shift={(current page.north${rtl ? ' west' : ''})}]
259
+ \shade [left color=c${g1}, right color=c${g2}, shading angle=45]
260
+ (current page.south west) rectangle (current page.north east);
261
+ ${content}
262
+ \end{tikzpicture}%`
263
+ }
264
+
265
+ interface Absolute {
266
+ rtl: boolean
267
+ content: string
268
+ }
269
+ export function absolute({ rtl, content }: Absolute) {
270
+ return String.raw`\begin{tikzpicture}[remember picture, overlay, shift={(current page.north${rtl ? ' west' : ''})}]
271
+ ${content}
272
+ \end{tikzpicture}%`
273
+ }
274
+
275
+ type TextBackground = z.infer<typeof TextBackground>
276
+ const TextBackground = z.object({
277
+ yshift: z.number(),
278
+ xshift: z.number(),
279
+ xscale: z.number(),
280
+ yscale: z.number(),
281
+ color: color,
282
+ opacity: z.number().min(.1).max(1),
283
+
284
+ })
285
+ export function txtBackground(raw: TextBackground) {
286
+ const { yshift, xshift, xscale, yscale, color, opacity } = TextBackground.parse(raw)
287
+ return String.raw`% TEXT BACKGROUND
288
+ \fill[
289
+ yshift=${yshift},
290
+ xshift=${xshift},
291
+ xscale=${xscale},
292
+ yscale=${yscale},
293
+ fill=${color},
294
+ opacity=${opacity}] svg "M 0.97 0.37 C 0.95 0.26 0.94 0.11 0.85 0.05 C 0.76 0.00 0.54 0.02 0.41 0.05 C 0.28 0.08 0.12 0.10 0.06 0.24 C 0.00 0.37 0.00 0.73 0.05 0.85 C 0.11 0.97 0.26 0.94 0.39 0.96 C 0.51 0.98 0.71 1.00 0.80 0.96 C 0.90 0.92 0.94 0.82 0.97 0.72 C 1.00 0.62 0.99 0.48 0.97 0.37 C 0.95 0.26 0.94 0.11 0.85 0.05";`
295
+ }
296
+
297
+
298
+ type Img = z.infer<typeof Img>
299
+ const Img = z.object({
300
+ src: z.string(),
301
+ })
302
+ export function img(raw: Img) {
303
+ const { src } = Img.parse(raw)
304
+ return String.raw`\begin{scope}
305
+ \clip[
306
+ scale=349,
307
+ xshift=-.5,
308
+ yshift=-.5,
309
+ ] svg "M 0.97 0.37 C 0.95 0.26 0.94 0.11 0.85 0.05 C 0.76 0.00 0.54 0.02 0.41 0.05 C 0.28 0.08 0.12 0.10 0.06 0.24 C 0.00 0.37 0.00 0.73 0.05 0.85 C 0.11 0.97 0.26 0.94 0.39 0.96 C 0.51 0.98 0.71 1.00 0.80 0.96 C 0.90 0.92 0.94 0.82 0.97 0.72 C 1.00 0.62 0.99 0.48 0.97 0.37 C 0.95 0.26 0.94 0.11 0.85 0.05";
310
+ \node[opacity=.75] at (0,0) {\includegraphics[width=12cm]{${src}}};
192
311
  \end{scope}`
193
- }
312
+ }
313
+
314
+ export function tikzpicture(content: string) {
315
+ return String.raw`\begin{tikzpicture}
316
+ ${content}
317
+ \end{tikzpicture}%`
318
+ }
319
+
320
+ interface Rect {
321
+ x: number
322
+ y: number
323
+ width: number
324
+ height: number
325
+ fill: string
326
+ opacity: number
327
+ }
328
+
329
+ type Tcolorbox = z.infer<typeof Tcolorbox>
330
+ const Tcolorbox = z.object({
331
+ width: z.number().min(0).max(1),
332
+ color: z.string(),
333
+ opacity: z.number().min(0).max(1),
334
+ arc: z.number(),
335
+ boxsep: z.number(),
336
+ halign: z.enum(['left', 'center', 'right']),
337
+ content: z.string(),
338
+ })
339
+
340
+ export function tcolorbox(raw: Tcolorbox) {
341
+ const { width, color, opacity, arc, boxsep, halign, content } = Tcolorbox.parse(raw)
342
+ return String.raw`\begin{tcolorbox}[
343
+ width=${width}\textwidth,
344
+ enhanced,
345
+ colback=${color},
346
+ colframe=${color},
347
+ opacityback=${opacity},
348
+ opacityframe=0,
349
+ arc=${arc}pt,
350
+ boxsep=${boxsep}cm,
351
+ halign=${halign},
352
+ valign=top]
353
+ ${content}
354
+ \end{tcolorbox}`
355
+ }
356
+
357
+ export function ragged(rtl: boolean) {
358
+ return rtl
359
+ ? `\\RaggedLeft`
360
+ : `\\RaggedRight`
361
+ }
362
+ export function vspace(cm: number) {
363
+ return `\\vspace{${cm}cm}`
364
+ }
365
+
366
+ export function Huge(text: string) {
367
+ return `\\Huge ${text}`
368
+ }
369
+
370
+ export function Large(text: string) {
371
+ return `\\Large ${text}`
372
+ }
373
+
374
+ export function large(text: string) {
375
+ return `\\large ${text}`
376
+ }
377
+
378
+ export function normalsize(text: string) {
379
+ return `\\normalsize ${text}`
380
+ }
381
+
382
+ export function small(text: string) {
383
+ return `\\small ${text}`
384
+ }
385
+
386
+ export function footnotesize(text: string) {
387
+ return `\\footnotesize ${text}`
388
+ }
389
+
390
+ export function scriptsize(text: string) {
391
+ return `\\scriptsize ${text}`
392
+ }
393
+
394
+ export function tiny(text: string) {
395
+ return `\\tiny ${text}`
396
+ }
397
+
398
+ export function bf(text: string) {
399
+ return `\\textbf{${text}}`
400
+ }
401
+
402
+ export function parskip(cm: number) {
403
+ return `\\setlength{\\parskip}{${cm}cm}`
404
+ }
405
+
406
+ interface Quote {
407
+ text: string
408
+ name: string
409
+ }
410
+
411
+ export function quote({ text, name }: Quote) {
412
+ return String.raw`
413
+ ${footnotesize(bf(`${text}`))} \\[.3cm]
414
+ \hfill ${tiny(`-${name}`)}`
415
+ }
416
+
417
+ export const vfill = `\\vspace*{\\fill}`
418
+ export const centering = '\\centering'
package/src/cover.ts CHANGED
@@ -1,13 +1,38 @@
1
1
 
2
2
  import assert from "assert"
3
3
  import { z } from "zod"
4
- import { colorMap, defineColors, Emoji, fromSvg, getColors, svgTex, type Transform } from "./common"
4
+ import { absolute, background, bf, centering, colorMap, defineColors, Emoji, footnotesize, fromSvg, getColors, gradient, img, Lang, Large, minipage, normalsize, parskip, poly, quote, ragged, svg, tcolorbox, tikzpicture, txtBackground, vfill, vspace } from "./common"
5
5
  import { emojiMap } from "./emojis"
6
6
 
7
+ const backBackground = String.raw`\fill[
8
+ white,
9
+ opacity=0.2]
10
+ ($(current page.north east) + (-0.025\paperwidth, -1cm)$)
11
+ rectangle ++(-0.45\paperwidth, -0.6\paperheight);
12
+ \end{tikzpicture}
13
+ `
14
+
15
+ const barcode = String.raw`\begin{tikzpicture}
16
+ % 1. Draw the white background rectangle with a thin border
17
+ \fill[white, opacity=0.8, rounded corners=2pt] (0,0) rectangle (4,2);
18
+
19
+ % 2. Generate "random" vertical lines for the barcode effect
20
+ % {position / thickness}
21
+ \foreach \x / \w in {
22
+ 0.3/1.5, 0.5/0.5, 0.7/2.0, 0.9/0.8,
23
+ 1.2/1.2, 1.4/0.4, 1.6/2.5, 1.9/1.0,
24
+ 2.2/0.6, 2.4/1.8, 2.7/0.5, 3.0/2.2,
25
+ 3.3/0.9, 3.5/1.4, 3.7/0.7%
26
+ } {
27
+ \draw[line width=\w pt, black] (\x, 0.2) -- (\x, 1.8);
28
+ }
29
+ \end{tikzpicture}
30
+ `
7
31
 
8
32
  export type Cover = z.infer<typeof Cover>
9
33
  export const Cover = z.object({
10
- gradient: z.array(z.string()).length(2),
34
+ lang: Lang,
35
+ gradient: gradient,
11
36
  emoji: Emoji,
12
37
  title: z.string().max(64),
13
38
  author: z.string().max(32),
@@ -16,11 +41,11 @@ export const Cover = z.object({
16
41
  testimonial_quote: z.string().max(256),
17
42
  testimonial_name: z.string().max(64),
18
43
  slogan: z.string().max(64),
19
- jpgBase64: z.string().max(500_000),
44
+ avifBase64: z.string().max(500_000),
20
45
  })
21
46
 
22
- const TRANSFORM: Transform = { x: -220, y: 60, scale: 2.3, rotate: -15 }
23
47
  export function coverTex({
48
+ lang,
24
49
  gradient,
25
50
  emoji,
26
51
  title,
@@ -37,127 +62,140 @@ export function coverTex({
37
62
  const el = fromSvg(emojiMap[emoji]!)
38
63
  const colors = colorMap(new Set([...gradient, ...el.flatMap(getColors)]))
39
64
 
65
+ const rtl = lang === 'he'
66
+
67
+ const titlePage = minipage({
68
+ vAlign: 't',
69
+ width: .5,
70
+ height: 1,
71
+ content: [
72
+ minipage({
73
+ vAlign: 'c',
74
+ width: 1,
75
+ height: .28,
76
+ content: [
77
+ centering,
78
+ Large(bf(title)),
79
+ '',
80
+ vspace(0.5),
81
+ normalsize(author)
82
+ ].join('\n')
83
+ }),
84
+ '',
85
+ vfill,
86
+ centering,
87
+ tikzpicture([
88
+ img({ src: 'cover.jpg' }),
89
+ ].join('\n')),
90
+ vfill,
91
+ ].join('\n')
92
+ })
93
+
94
+ const backPage = minipage({
95
+ vAlign: 't',
96
+ width: .5,
97
+ height: 1,
98
+ content: [
99
+ centering,
100
+ vspace(1),
101
+ tcolorbox({
102
+ width: 0.9,
103
+ color: 'white',
104
+ opacity: 0.4,
105
+ arc: 5,
106
+ boxsep: .4,
107
+ halign: rtl ? 'right' : 'left',
108
+ content: minipage({
109
+ vAlign: 't',
110
+ width: 1,
111
+ height: .6,
112
+ content: [
113
+ ragged(rtl),
114
+ parskip(0.3),
115
+ normalsize(bf(tagline)),
116
+ '',
117
+ vspace(0.3),
118
+ footnotesize(blurb),
119
+ '',
120
+ vfill,
121
+ quote({
122
+ text: testimonial_quote,
123
+ name: testimonial_name,
124
+ })
125
+ ].join('\n')
126
+ })
127
+ }),
128
+ '',
129
+ vfill,
130
+ barcode,
131
+ '',
132
+ 'https://booky.kids',
133
+ '',
134
+ vspace(0.2),
135
+ footnotesize(slogan),
136
+ '',
137
+ vfill,
138
+ ].join('\n')
139
+ })
140
+
141
+ const pages = [backPage, titlePage]
142
+ const doc = [
143
+ // BACKGROUND
144
+ background({
145
+ rtl,
146
+ gradient,
147
+ colors,
148
+ content: txtBackground({
149
+ yshift: -145,
150
+ xshift: 0,
151
+ xscale: 410,
152
+ yscale: 130,
153
+ color: 'blue!10',
154
+ opacity: 0.4
155
+ })
156
+ }),
157
+
158
+ ...(rtl ? pages.reverse() : pages),
159
+ // EMOJI
160
+ absolute({
161
+ rtl,
162
+ content: [
163
+ svg({
164
+ x: 10,
165
+ y: 220,
166
+ scale: 2.3,
167
+ rotate: -15,
168
+ colors,
169
+ elements: el,
170
+ })
171
+ ].join('\n')
172
+ })
173
+ ]
174
+
40
175
  return String.raw`
41
176
  \documentclass[17pt]{extarticle}
42
177
  \usepackage[a4paper, landscape, margin=0cm]{geometry}
43
178
  \usepackage{setspace}
179
+ \usepackage[most]{tcolorbox}
180
+ \usepackage[none]{hyphenat}
181
+ \usepackage{ragged2e}
182
+
183
+
44
184
  \usepackage{tikz}
45
185
  \usetikzlibrary{svg.path}
46
186
  \usetikzlibrary{calc}
47
187
 
48
- \usepackage{polyglossia}
49
- \setmainlanguage{hebrew}
50
- % Ensure this file exists in your project folder!
51
- \newfontfamily\hebrewfont[
52
- Script=Hebrew,
53
- Path=./,
54
- Extension=.ttf,
55
- UprightFont=*-Regular,
56
- BoldFont=*-Bold
57
- ]{Fredoka}
58
-
188
+ \setlength{\fboxsep}{0pt}
189
+ \setlength{\fboxrule}{0pt}
190
+ ${poly(lang)}
59
191
  \pagestyle{empty}
60
192
 
61
193
  \begin{document}
194
+ ${ragged(rtl)}
62
195
  ${defineColors(colors)}
63
-
64
- \begin{tikzpicture}[remember picture, overlay]
65
- \shade [shading=axis, shading angle=45, left color=c${colors[c1]}, right color=c${colors[c2]}]
66
- (current page.south west) rectangle (current page.north east);
67
-
68
- \fill[
69
- yshift=-130,
70
- xshift=-20,
71
- xscale=410,
72
- yscale=120,
73
- blue!10,
74
- opacity=0.4] svg "M 0.97 0.37 C 0.95 0.26 0.94 0.11 0.85 0.05 C 0.76 0.00 0.54 0.02 0.41 0.05 C 0.28 0.08 0.12 0.10 0.06 0.24 C 0.00 0.37 0.00 0.73 0.05 0.85 C 0.11 0.97 0.26 0.94 0.39 0.96 C 0.51 0.98 0.71 1.00 0.80 0.96 C 0.90 0.92 0.94 0.82 0.97 0.72 C 1.00 0.62 0.99 0.48 0.97 0.37 C 0.95 0.26 0.94 0.11 0.85 0.05";
75
-
76
- \fill[
77
- white,
78
- opacity=0.2]
79
- ($(current page.north east) + (-0.025\paperwidth, -1cm)$)
80
- rectangle ++(-0.45\paperwidth, -0.6\paperheight);
81
- \end{tikzpicture}
82
-
196
+ \setlength{\parindent}{0pt}
83
197
  \noindent
84
- \begin{minipage}[c][0.95\textheight]{0.5\textwidth}
85
- \centering
86
- \begin{minipage}[t][0.6\textheight]{.8\textwidth}
87
- \vspace{1cm}
88
- \small
89
- \textbf{${tagline}}
90
-
91
- \vspace{.5cm}
92
- \scriptsize
93
- \begin{spacing}{1.5}
94
- \setlength{\parskip}{.8em}
95
- ${blurb}
96
- \end{spacing}
97
- \vspace{\fill}
98
-
99
- \vspace{0.5cm}
100
- \footnotesize
101
- \textbf{"${testimonial_quote}"}
102
-
103
- \vspace{0.3cm}
104
- \tiny
105
- \hfill
106
- - ${testimonial_name}
107
- \end{minipage}
108
- \vspace{\fill}
109
- \begin{minipage}[c][0.25\textheight]{.8\textwidth}
110
- \centering
111
- \vspace{\fill}
112
- \begin{tikzpicture}
113
- % 1. Draw the white background rectangle with a thin border
114
- \fill[white, opacity=0.8, rounded corners=2pt] (0,0) rectangle (4,2);
115
-
116
- % 2. Generate "random" vertical lines for the barcode effect
117
- % {position / thickness}
118
- \foreach \x / \w in {
119
- 0.3/1.5, 0.5/0.5, 0.7/2.0, 0.9/0.8,
120
- 1.2/1.2, 1.4/0.4, 1.6/2.5, 1.9/1.0,
121
- 2.2/0.6, 2.4/1.8, 2.7/0.5, 3.0/2.2,
122
- 3.3/0.9, 3.5/1.4, 3.7/0.7%
123
- } {
124
- \draw[line width=\w pt, black] (\x, 0.2) -- (\x, 1.8);
125
- }
126
- \end{tikzpicture}
127
-
128
- \vspace{\fill}
129
- \small
130
- https://booky.kids
131
-
132
- \vspace{0.2cm}
133
- ${slogan}
134
-
135
- \end{minipage}
136
- \end{minipage}
137
- %
138
- %
139
- \begin{minipage}[c][0.95\textheight]{0.5\textwidth}
140
- \begin{minipage}[c][0.25\textheight]{\textwidth}
141
- \centering
142
- \large
143
- \textbf{${title}} \\[.1cm]
144
- \small
145
- ${author}
146
- \end{minipage}
147
- \begin{minipage}[c][0.7\textheight]{\textwidth}
148
- \centering
149
- \begin{tikzpicture}
150
- \begin{scope}
151
- \clip[
152
- yshift=-170,
153
- xshift=-170,
154
- scale=340] svg "M 0.97 0.37 C 0.95 0.26 0.94 0.11 0.85 0.05 C 0.76 0.00 0.54 0.02 0.41 0.05 C 0.28 0.08 0.12 0.10 0.06 0.24 C 0.00 0.37 0.00 0.73 0.05 0.85 C 0.11 0.97 0.26 0.94 0.39 0.96 C 0.51 0.98 0.71 1.00 0.80 0.96 C 0.90 0.92 0.94 0.82 0.97 0.72 C 1.00 0.62 0.99 0.48 0.97 0.37 C 0.95 0.26 0.94 0.11 0.85 0.05";
155
- \node[opacity=.75] at (0,0) {\includegraphics[width=12cm]{cover.jpg}};
156
- \end{scope}
157
- ${svgTex(TRANSFORM, el, colors)}
158
- \end{tikzpicture}
159
- \end{minipage}
160
- \end{minipage}
198
+ ${(rtl ? doc.reverse() : doc).join('\n')}
161
199
 
162
200
  \end{document}
163
201
  `
package/src/server.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { $ } from "bun"
2
2
  import { mkdir, writeFile } from "node:fs/promises"
3
3
  import PQueue from "p-queue"
4
+ import sharp from "sharp"
4
5
  import { Book, bookTex } from "./book"
5
6
  import { Cover, coverTex } from "./cover"
6
7
 
@@ -22,6 +23,11 @@ interface Build {
22
23
  tex: string
23
24
  }
24
25
 
26
+ async function jpg(base64: string) {
27
+ const b = Buffer.from(base64, 'base64')
28
+ return await sharp(b).jpeg({ quality: 90 }).toBuffer()
29
+ }
30
+
25
31
  async function pdf({ tmp, tex }: Build) {
26
32
 
27
33
  await writeFile(`${tmp}/main.tex`, tex)
@@ -46,11 +52,11 @@ async function book(req: Request) {
46
52
  const book = Book.parse(body)
47
53
 
48
54
  const tmp = await tmpDir()
49
- await writeFile(`${tmp}/hero.jpg`, Buffer.from(book.heroJpgBase64, 'base64'))
50
- await Promise.all(book.pages.map(async (page, i) => {
55
+ await writeFile(`${tmp}/hero.jpg`, await jpg(book.heroAvifBase64))
56
+ for (const [i, page] of book.pages.entries()) {
51
57
  console.log(`Writing page ${i} jpg`)
52
- writeFile(`${tmp}/${i}.jpg`, Buffer.from(page.jpgBase64, 'base64'))
53
- }))
58
+ await writeFile(`${tmp}/${i}.jpg`, await jpg(page.avifBase64))
59
+ }
54
60
 
55
61
  const tex = bookTex(book)
56
62
  return pdf({ tmp, tex })
@@ -62,7 +68,7 @@ async function cover(req: Request) {
62
68
 
63
69
  const tex = coverTex(cover)
64
70
  const tmp = await tmpDir()
65
- await writeFile(`${tmp}/cover.jpg`, Buffer.from(cover.jpgBase64, 'base64'))
71
+ await writeFile(`${tmp}/cover.jpg`, await jpg(cover.avifBase64))
66
72
  return pdf({ tmp, tex })
67
73
  }
68
74