@studious-lms/server 1.1.13 → 1.1.15
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/dist/index.js +3 -0
- package/dist/lib/jsonConversion.d.ts +3 -0
- package/dist/lib/jsonConversion.d.ts.map +1 -0
- package/dist/lib/jsonConversion.js +517 -0
- package/dist/lib/jsonStyles.d.ts +29 -0
- package/dist/lib/jsonStyles.d.ts.map +1 -0
- package/dist/lib/jsonStyles.js +28 -0
- package/dist/routers/_app.d.ts +82 -4
- package/dist/routers/_app.d.ts.map +1 -1
- package/dist/routers/assignment.d.ts +30 -0
- package/dist/routers/assignment.d.ts.map +1 -1
- package/dist/routers/class.d.ts.map +1 -1
- package/dist/routers/class.js +13 -3
- package/dist/routers/conversation.d.ts +1 -1
- package/dist/routers/file.d.ts +4 -0
- package/dist/routers/file.d.ts.map +1 -1
- package/dist/routers/labChat.d.ts +1 -1
- package/dist/routers/labChat.d.ts.map +1 -1
- package/dist/routers/labChat.js +159 -7
- package/dist/routers/message.d.ts +5 -0
- package/dist/routers/message.d.ts.map +1 -1
- package/dist/routers/message.js +18 -5
- package/dist/utils/inference.d.ts +5 -0
- package/dist/utils/inference.d.ts.map +1 -1
- package/dist/utils/inference.js +16 -0
- package/package.json +2 -1
- package/prisma/schema.prisma +6 -0
- package/src/index.ts +4 -0
- package/src/lib/jsonConversion.ts +537 -0
- package/src/lib/jsonStyles.ts +36 -0
- package/src/routers/class.ts +14 -3
- package/src/routers/labChat.ts +166 -7
- package/src/routers/message.ts +18 -5
- package/src/utils/inference.ts +19 -0
package/dist/index.js
CHANGED
|
@@ -78,6 +78,9 @@ app.use((req, res, next) => {
|
|
|
78
78
|
});
|
|
79
79
|
// Create HTTP server
|
|
80
80
|
const httpServer = createServer(app);
|
|
81
|
+
app.get('/health', (req, res) => {
|
|
82
|
+
res.status(200).json({ message: 'OK' });
|
|
83
|
+
});
|
|
81
84
|
// Setup Socket.IO
|
|
82
85
|
const io = new Server(httpServer, {
|
|
83
86
|
cors: {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsonConversion.d.ts","sourceRoot":"","sources":["../../src/lib/jsonConversion.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAsB,MAAM,cAAc,CAAA;AAEhE,wBAAsB,SAAS,CAAC,MAAM,EAAE,aAAa,EAAE,wCAohBtD"}
|
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
import { PDFDocument, StandardFonts, rgb } from 'pdf-lib';
|
|
2
|
+
import { FormatTypes, Fonts } from './jsonStyles';
|
|
3
|
+
export async function createPdf(blocks) {
|
|
4
|
+
console.log('createPdf: Starting PDF creation with', blocks.length, 'blocks');
|
|
5
|
+
try {
|
|
6
|
+
const pdfDoc = await PDFDocument.create();
|
|
7
|
+
console.log('createPdf: PDFDocument created successfully');
|
|
8
|
+
const timesRomanFont = await pdfDoc.embedFont(StandardFonts.TimesRoman);
|
|
9
|
+
const courierFont = await pdfDoc.embedFont(StandardFonts.Courier);
|
|
10
|
+
const helveticaFont = await pdfDoc.embedFont(StandardFonts.Helvetica);
|
|
11
|
+
const helveticaBoldFont = await pdfDoc.embedFont(StandardFonts.HelveticaBold);
|
|
12
|
+
const helveticaItalicFont = await pdfDoc.embedFont(StandardFonts.HelveticaOblique);
|
|
13
|
+
const helveticaBoldItalicFont = await pdfDoc.embedFont(StandardFonts.HelveticaBoldOblique);
|
|
14
|
+
const defaultFont = helveticaFont;
|
|
15
|
+
const defaultParagraphSpacing = 10;
|
|
16
|
+
const defaultLineHeight = 1.3;
|
|
17
|
+
const defaultFontSize = 12;
|
|
18
|
+
const defaultIndentWidth = 14;
|
|
19
|
+
const defaultPadding = 10;
|
|
20
|
+
const headingColor = rgb(0.1, 0.1, 0.1);
|
|
21
|
+
const paragraphColor = rgb(0.15, 0.15, 0.15);
|
|
22
|
+
const FONTS = {
|
|
23
|
+
[Fonts.TIMES_ROMAN]: timesRomanFont,
|
|
24
|
+
[Fonts.COURIER]: courierFont,
|
|
25
|
+
[Fonts.HELVETICA]: helveticaFont,
|
|
26
|
+
[Fonts.HELVETICA_BOLD]: helveticaBoldFont,
|
|
27
|
+
[Fonts.HELVETICA_ITALIC]: helveticaItalicFont,
|
|
28
|
+
[Fonts.HELVETICA_BOLD_ITALIC]: helveticaBoldItalicFont,
|
|
29
|
+
};
|
|
30
|
+
const STYLE_PRESETS = {
|
|
31
|
+
[FormatTypes.HEADER_1]: { fontSize: 28, lineHeight: 1.35, font: helveticaBoldFont, color: headingColor },
|
|
32
|
+
[FormatTypes.HEADER_2]: { fontSize: 22, lineHeight: 1.35, font: helveticaBoldFont, color: headingColor },
|
|
33
|
+
[FormatTypes.HEADER_3]: { fontSize: 18, lineHeight: 1.35, font: helveticaBoldFont, color: headingColor },
|
|
34
|
+
[FormatTypes.HEADER_4]: { fontSize: 16, lineHeight: 1.3, font: helveticaBoldFont, color: headingColor },
|
|
35
|
+
[FormatTypes.HEADER_5]: { fontSize: 14, lineHeight: 1.3, font: helveticaBoldFont, color: headingColor },
|
|
36
|
+
[FormatTypes.HEADER_6]: { fontSize: 12, lineHeight: 1.3, font: helveticaBoldFont, color: headingColor },
|
|
37
|
+
[FormatTypes.QUOTE]: { fontSize: 14, lineHeight: 1.5, color: rgb(0.35, 0.35, 0.35) },
|
|
38
|
+
[FormatTypes.CODE_BLOCK]: { fontSize: 12, lineHeight: 1.6, font: courierFont, color: rgb(0.1, 0.1, 0.1), background: rgb(0.95, 0.95, 0.95) },
|
|
39
|
+
[FormatTypes.PARAGRAPH]: { fontSize: 12, lineHeight: 1.3, color: paragraphColor },
|
|
40
|
+
[FormatTypes.BULLET]: { fontSize: 12, lineHeight: 1.3, color: paragraphColor },
|
|
41
|
+
[FormatTypes.NUMBERED]: { fontSize: 12, lineHeight: 1.3, color: paragraphColor },
|
|
42
|
+
[FormatTypes.TABLE]: { fontSize: 12, lineHeight: 1.3, color: paragraphColor },
|
|
43
|
+
[FormatTypes.IMAGE]: { fontSize: 12, lineHeight: 1.3 },
|
|
44
|
+
};
|
|
45
|
+
const hexToRgb = (hex) => {
|
|
46
|
+
if (hex.length == 7) {
|
|
47
|
+
const r = parseInt(hex.slice(1, 3), 16) / 255.0;
|
|
48
|
+
const g = parseInt(hex.slice(3, 5), 16) / 255.0;
|
|
49
|
+
const b = parseInt(hex.slice(5, 7), 16) / 255.0;
|
|
50
|
+
return rgb(r, g, b);
|
|
51
|
+
}
|
|
52
|
+
else if (hex.length == 4) {
|
|
53
|
+
const r = parseInt(hex.slice(1, 2), 16) / 15.0;
|
|
54
|
+
const g = parseInt(hex.slice(2, 3), 16) / 15.0;
|
|
55
|
+
const b = parseInt(hex.slice(3, 4), 16) / 15.0;
|
|
56
|
+
return rgb(r, g, b);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
return rgb(0.0, 0.0, 0.0);
|
|
60
|
+
}
|
|
61
|
+
;
|
|
62
|
+
};
|
|
63
|
+
const colorParse = (color) => {
|
|
64
|
+
if (typeof color === 'string') {
|
|
65
|
+
return hexToRgb(color);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
return color;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
// Function to replace Unicode characters that can't be encoded by WinAnsi
|
|
72
|
+
const sanitizeText = (text) => {
|
|
73
|
+
return text
|
|
74
|
+
.replace(/→/g, '->') // Right arrow
|
|
75
|
+
.replace(/←/g, '<-') // Left arrow
|
|
76
|
+
.replace(/↑/g, '^') // Up arrow
|
|
77
|
+
.replace(/↓/g, 'v') // Down arrow
|
|
78
|
+
.replace(/•/g, '*') // Bullet point
|
|
79
|
+
.replace(/–/g, '-') // En dash
|
|
80
|
+
.replace(/—/g, '--') // Em dash
|
|
81
|
+
.replace(/'/g, "'") // Left single quote
|
|
82
|
+
.replace(/'/g, "'") // Right single quote
|
|
83
|
+
.replace(/"/g, '"') // Left double quote
|
|
84
|
+
.replace(/"/g, '"') // Right double quote
|
|
85
|
+
.replace(/…/g, '...') // Ellipsis
|
|
86
|
+
.replace(/°/g, ' degrees') // Degree symbol
|
|
87
|
+
.replace(/±/g, '+/-') // Plus-minus
|
|
88
|
+
.replace(/×/g, 'x') // Multiplication sign
|
|
89
|
+
.replace(/÷/g, '/'); // Division sign
|
|
90
|
+
// Add more replacements as needed
|
|
91
|
+
};
|
|
92
|
+
let page = pdfDoc.addPage();
|
|
93
|
+
let { width, height } = page.getSize();
|
|
94
|
+
const { marginTop, marginBottom, marginLeft, marginRight } = { marginTop: 50, marginBottom: 50, marginLeft: 50, marginRight: 50 };
|
|
95
|
+
const maxTextWidth = () => width - marginLeft - marginRight;
|
|
96
|
+
const wrapText = (text, font, fontSize, maxWidth) => {
|
|
97
|
+
if (!text)
|
|
98
|
+
return [''];
|
|
99
|
+
const words = text.split(/\s+/);
|
|
100
|
+
const lines = [];
|
|
101
|
+
let current = '';
|
|
102
|
+
const measure = (t) => font.widthOfTextAtSize(t, fontSize);
|
|
103
|
+
const pushCurrent = () => {
|
|
104
|
+
if (current.length > 0) {
|
|
105
|
+
lines.push(current);
|
|
106
|
+
current = '';
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
for (let i = 0; i < words.length; i++) {
|
|
110
|
+
const word = words[i];
|
|
111
|
+
if (current.length === 0) {
|
|
112
|
+
// If a single word is too long, hard-break it
|
|
113
|
+
if (measure(word) > maxWidth) {
|
|
114
|
+
let chunk = '';
|
|
115
|
+
for (const ch of word) {
|
|
116
|
+
const test = chunk + ch;
|
|
117
|
+
if (measure(test) > maxWidth && chunk.length > 0) {
|
|
118
|
+
lines.push(chunk);
|
|
119
|
+
chunk = ch;
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
chunk = test;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
current = chunk;
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
current = word;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
const test = current + ' ' + word;
|
|
133
|
+
if (measure(test) <= maxWidth) {
|
|
134
|
+
current = test;
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
pushCurrent();
|
|
138
|
+
// start new line with this word; hard-break if needed
|
|
139
|
+
if (measure(word) > maxWidth) {
|
|
140
|
+
let chunk = '';
|
|
141
|
+
for (const ch of word) {
|
|
142
|
+
const t2 = chunk + ch;
|
|
143
|
+
if (measure(t2) > maxWidth && chunk.length > 0) {
|
|
144
|
+
lines.push(chunk);
|
|
145
|
+
chunk = ch;
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
chunk = t2;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
current = chunk;
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
current = word;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
pushCurrent();
|
|
160
|
+
return lines;
|
|
161
|
+
};
|
|
162
|
+
let y = height - marginTop;
|
|
163
|
+
let lastLineHeight = -1;
|
|
164
|
+
console.log('createPdf: Starting to process', blocks.length, 'blocks');
|
|
165
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
166
|
+
const block = blocks[i];
|
|
167
|
+
console.log(`createPdf: Processing block ${i + 1}/${blocks.length}, format: ${block.format}, content type: ${typeof block.content}`);
|
|
168
|
+
try {
|
|
169
|
+
const preset = STYLE_PRESETS[block.format] || { fontSize: defaultFontSize, lineHeight: defaultLineHeight };
|
|
170
|
+
const userLineHeight = block.metadata?.lineHeight;
|
|
171
|
+
const fontSize = block.metadata?.fontSize || preset.fontSize;
|
|
172
|
+
const lineHeight = (userLineHeight ? fontSize * userLineHeight : fontSize * preset.lineHeight);
|
|
173
|
+
const paragraphSpacing = block.metadata?.paragraphSpacing || defaultParagraphSpacing;
|
|
174
|
+
const indentWidth = block.metadata?.indentWidth || defaultIndentWidth;
|
|
175
|
+
const paddingX = block.metadata?.paddingX || defaultPadding;
|
|
176
|
+
const paddingY = block.metadata?.paddingY || defaultPadding;
|
|
177
|
+
const font = FONTS[block.metadata?.font] || preset.font || defaultFont; // Broken
|
|
178
|
+
const color = colorParse(block.metadata?.color || preset.color || rgb(0.0, 0.0, 0.0));
|
|
179
|
+
const background = colorParse(block.metadata?.background || preset.background || rgb(1.0, 1.0, 1.0));
|
|
180
|
+
if (lastLineHeight >= 0) {
|
|
181
|
+
y -= lineHeight - lastLineHeight;
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
y -= fontSize;
|
|
185
|
+
}
|
|
186
|
+
const ensureSpace = (needed) => {
|
|
187
|
+
if (y - needed < marginBottom) {
|
|
188
|
+
page = pdfDoc.addPage();
|
|
189
|
+
({ width, height } = page.getSize());
|
|
190
|
+
y = height - marginTop - fontSize;
|
|
191
|
+
lastLineHeight = -1;
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
const drawParagraph = (text) => {
|
|
199
|
+
const sanitizedText = sanitizeText(text);
|
|
200
|
+
const lines = wrapText(sanitizedText, font, fontSize, maxTextWidth());
|
|
201
|
+
for (const line of lines) {
|
|
202
|
+
ensureSpace(lineHeight);
|
|
203
|
+
page.drawText(line, {
|
|
204
|
+
x: marginLeft,
|
|
205
|
+
y: y,
|
|
206
|
+
size: fontSize,
|
|
207
|
+
font: font,
|
|
208
|
+
color: color,
|
|
209
|
+
});
|
|
210
|
+
y -= lineHeight;
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
const drawHeading = (text, align) => {
|
|
214
|
+
const sanitizedText = sanitizeText(text);
|
|
215
|
+
const lines = wrapText(sanitizedText, font, fontSize, maxTextWidth());
|
|
216
|
+
for (const line of lines) {
|
|
217
|
+
ensureSpace(lineHeight);
|
|
218
|
+
let x = marginLeft;
|
|
219
|
+
if (align === 'center') {
|
|
220
|
+
const tw = font.widthOfTextAtSize(line, fontSize);
|
|
221
|
+
x = marginLeft + (maxTextWidth() - tw) / 2;
|
|
222
|
+
}
|
|
223
|
+
else if (align === 'right') {
|
|
224
|
+
const tw = font.widthOfTextAtSize(line, fontSize);
|
|
225
|
+
x = marginLeft + maxTextWidth() - tw;
|
|
226
|
+
}
|
|
227
|
+
page.drawText(line, {
|
|
228
|
+
x,
|
|
229
|
+
y: y,
|
|
230
|
+
size: fontSize,
|
|
231
|
+
font: font,
|
|
232
|
+
color: color,
|
|
233
|
+
});
|
|
234
|
+
y -= lineHeight;
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
const drawBulletList = (items) => {
|
|
238
|
+
const bulletIndent = indentWidth;
|
|
239
|
+
const gap = 8;
|
|
240
|
+
const contentWidth = maxTextWidth() - (bulletIndent + gap);
|
|
241
|
+
for (const item of items) {
|
|
242
|
+
const sanitizedItem = sanitizeText(item);
|
|
243
|
+
const lines = wrapText(sanitizedItem, font, fontSize, contentWidth);
|
|
244
|
+
ensureSpace(lineHeight);
|
|
245
|
+
// Bullet glyph (use ASCII bullet instead of Unicode)
|
|
246
|
+
page.drawText('*', {
|
|
247
|
+
x: marginLeft + gap,
|
|
248
|
+
y: y,
|
|
249
|
+
size: fontSize,
|
|
250
|
+
font: font,
|
|
251
|
+
color: color,
|
|
252
|
+
});
|
|
253
|
+
// First line
|
|
254
|
+
page.drawText(lines[0], {
|
|
255
|
+
x: marginLeft + bulletIndent + gap,
|
|
256
|
+
y: y,
|
|
257
|
+
size: fontSize,
|
|
258
|
+
font: font,
|
|
259
|
+
color: color,
|
|
260
|
+
});
|
|
261
|
+
y -= lineHeight;
|
|
262
|
+
// Continuation lines with hanging indent
|
|
263
|
+
for (let i = 1; i < lines.length; i++) {
|
|
264
|
+
ensureSpace(lineHeight);
|
|
265
|
+
page.drawText(lines[i], {
|
|
266
|
+
x: marginLeft + bulletIndent + gap,
|
|
267
|
+
y: y,
|
|
268
|
+
size: fontSize,
|
|
269
|
+
font: font,
|
|
270
|
+
color: color,
|
|
271
|
+
});
|
|
272
|
+
y -= lineHeight;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
const drawNumberedList = (items) => {
|
|
277
|
+
const numberIndent = indentWidth;
|
|
278
|
+
const gap = 8;
|
|
279
|
+
const contentWidth = maxTextWidth() - (numberIndent + gap);
|
|
280
|
+
let index = 1;
|
|
281
|
+
for (const item of items) {
|
|
282
|
+
const numLabel = `${index}.`;
|
|
283
|
+
const numWidth = font.widthOfTextAtSize(numLabel, fontSize);
|
|
284
|
+
const sanitizedItem = sanitizeText(item);
|
|
285
|
+
const lines = wrapText(sanitizedItem, font, fontSize, contentWidth);
|
|
286
|
+
ensureSpace(lineHeight);
|
|
287
|
+
page.drawText(numLabel, {
|
|
288
|
+
x: marginLeft + gap,
|
|
289
|
+
y: y,
|
|
290
|
+
size: fontSize,
|
|
291
|
+
font: font,
|
|
292
|
+
color: color,
|
|
293
|
+
});
|
|
294
|
+
page.drawText(lines[0], {
|
|
295
|
+
x: marginLeft + Math.max(numberIndent, numWidth + 6) + gap,
|
|
296
|
+
y: y,
|
|
297
|
+
size: fontSize,
|
|
298
|
+
font: font,
|
|
299
|
+
color: color,
|
|
300
|
+
});
|
|
301
|
+
y -= lineHeight;
|
|
302
|
+
for (let i = 1; i < lines.length; i++) {
|
|
303
|
+
ensureSpace(lineHeight);
|
|
304
|
+
page.drawText(lines[i], {
|
|
305
|
+
x: marginLeft + Math.max(numberIndent, numWidth + 6) + gap,
|
|
306
|
+
y: y,
|
|
307
|
+
size: fontSize,
|
|
308
|
+
font: font,
|
|
309
|
+
color: color,
|
|
310
|
+
});
|
|
311
|
+
y -= lineHeight;
|
|
312
|
+
}
|
|
313
|
+
index++;
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
const drawQuote = (text) => {
|
|
317
|
+
const ruleWidth = 2;
|
|
318
|
+
const ruleGap = 8;
|
|
319
|
+
const contentX = marginLeft + ruleWidth + ruleGap;
|
|
320
|
+
const contentWidth = maxTextWidth() - (ruleWidth + ruleGap);
|
|
321
|
+
const sanitizedText = sanitizeText(text);
|
|
322
|
+
const lines = wrapText(sanitizedText, font, fontSize, contentWidth);
|
|
323
|
+
const totalHeight = lines.length * lineHeight + fontSize;
|
|
324
|
+
var remainingHeight = totalHeight;
|
|
325
|
+
for (const line of lines) {
|
|
326
|
+
let pageAdded = ensureSpace(lineHeight);
|
|
327
|
+
if (pageAdded || remainingHeight == totalHeight) {
|
|
328
|
+
let blockHeight = Math.floor(Math.min(remainingHeight, y - marginBottom) / lineHeight) * lineHeight; // Get remaining height on page
|
|
329
|
+
page.drawRectangle({
|
|
330
|
+
x: marginLeft,
|
|
331
|
+
y: y + lineHeight,
|
|
332
|
+
width: ruleWidth,
|
|
333
|
+
height: -blockHeight - lineHeight + fontSize,
|
|
334
|
+
color: color,
|
|
335
|
+
});
|
|
336
|
+
remainingHeight -= blockHeight + lineHeight - fontSize;
|
|
337
|
+
}
|
|
338
|
+
page.drawText(line, {
|
|
339
|
+
x: contentX,
|
|
340
|
+
y: y,
|
|
341
|
+
size: fontSize,
|
|
342
|
+
font: font,
|
|
343
|
+
color: color,
|
|
344
|
+
});
|
|
345
|
+
y -= lineHeight;
|
|
346
|
+
}
|
|
347
|
+
y -= lineHeight - fontSize;
|
|
348
|
+
};
|
|
349
|
+
const drawCodeBlock = (textLines) => {
|
|
350
|
+
const codeFont = font;
|
|
351
|
+
// Detect indentation patterns
|
|
352
|
+
const detectIndentation = (lines) => {
|
|
353
|
+
let tabCount = 0;
|
|
354
|
+
let spaceCount = 0;
|
|
355
|
+
let minSpaces = Infinity;
|
|
356
|
+
for (const line of lines) {
|
|
357
|
+
if (line.trim().length === 0)
|
|
358
|
+
continue; // Skip empty lines
|
|
359
|
+
const leadingWhitespace = line.match(/^(\s*)/)?.[1] || '';
|
|
360
|
+
if (leadingWhitespace.includes('\t')) {
|
|
361
|
+
tabCount++;
|
|
362
|
+
}
|
|
363
|
+
else if (leadingWhitespace.length > 0) {
|
|
364
|
+
spaceCount++;
|
|
365
|
+
minSpaces = Math.min(minSpaces, leadingWhitespace.length);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
// Determine indentation strategy
|
|
369
|
+
if (tabCount > spaceCount) {
|
|
370
|
+
return { type: 'tab', width: indentWidth };
|
|
371
|
+
}
|
|
372
|
+
else if (spaceCount > 0) {
|
|
373
|
+
// Use the most common space count, or default to 4
|
|
374
|
+
const commonSpaces = minSpaces === Infinity ? 4 : minSpaces;
|
|
375
|
+
return { type: 'space', width: commonSpaces * (fontSize * 0.6) }; // Approximate space width
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
return { type: 'space', width: fontSize * 2.4 }; // Default 4 spaces
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
const indentInfo = detectIndentation(textLines);
|
|
382
|
+
// Process lines with indentation
|
|
383
|
+
const processedLines = [];
|
|
384
|
+
for (const line of textLines) {
|
|
385
|
+
const leadingWhitespace = line.match(/^(\s*)/)?.[1] || '';
|
|
386
|
+
let indentLevel = 0;
|
|
387
|
+
if (indentInfo.type === 'tab') {
|
|
388
|
+
indentLevel = leadingWhitespace.split('\t').length - 1;
|
|
389
|
+
}
|
|
390
|
+
else {
|
|
391
|
+
// Count spaces, grouping by the detected space width
|
|
392
|
+
const spaceWidth = indentInfo.width / (fontSize * 0.6); // Convert back to space count
|
|
393
|
+
indentLevel = Math.floor(leadingWhitespace.length / spaceWidth);
|
|
394
|
+
}
|
|
395
|
+
processedLines.push({
|
|
396
|
+
text: line.trim(),
|
|
397
|
+
indentLevel,
|
|
398
|
+
originalLine: line
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
// Wrap each processed line separately to preserve line breaks and indentation
|
|
402
|
+
const wrappedLines = [];
|
|
403
|
+
const contentW = maxTextWidth() - paddingX * 2;
|
|
404
|
+
for (const processedLine of processedLines) {
|
|
405
|
+
if (processedLine.text.length === 0) {
|
|
406
|
+
// Empty line - preserve as empty line
|
|
407
|
+
wrappedLines.push({
|
|
408
|
+
text: '',
|
|
409
|
+
indentLevel: processedLine.indentLevel,
|
|
410
|
+
isContinuation: false
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
else {
|
|
414
|
+
const parts = wrapText(processedLine.text, codeFont, fontSize, contentW);
|
|
415
|
+
for (let i = 0; i < parts.length; i++) {
|
|
416
|
+
wrappedLines.push({
|
|
417
|
+
text: parts[i],
|
|
418
|
+
indentLevel: processedLine.indentLevel,
|
|
419
|
+
isContinuation: i > 0
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
const totalHeight = wrappedLines.length * lineHeight + paddingY * 2;
|
|
425
|
+
var remainingHeight = totalHeight;
|
|
426
|
+
y -= paddingY;
|
|
427
|
+
for (const wrappedLine of wrappedLines) {
|
|
428
|
+
let pageAdded = ensureSpace(lineHeight + paddingY);
|
|
429
|
+
if (pageAdded || remainingHeight == totalHeight) {
|
|
430
|
+
let blockHeight = Math.floor(Math.min(remainingHeight, y - marginBottom - paddingY) / lineHeight) * lineHeight;
|
|
431
|
+
page.drawRectangle({
|
|
432
|
+
x: marginLeft,
|
|
433
|
+
y: y + fontSize,
|
|
434
|
+
width: maxTextWidth(),
|
|
435
|
+
height: -blockHeight - paddingY * 2,
|
|
436
|
+
color: background,
|
|
437
|
+
});
|
|
438
|
+
remainingHeight -= blockHeight;
|
|
439
|
+
y -= paddingY;
|
|
440
|
+
}
|
|
441
|
+
// Calculate indentation offset
|
|
442
|
+
const indentOffset = wrappedLine.indentLevel * indentInfo.width;
|
|
443
|
+
page.drawText(wrappedLine.text, {
|
|
444
|
+
x: marginLeft + paddingX + indentOffset,
|
|
445
|
+
y: y,
|
|
446
|
+
size: fontSize,
|
|
447
|
+
font: codeFont,
|
|
448
|
+
color: color,
|
|
449
|
+
});
|
|
450
|
+
y -= lineHeight;
|
|
451
|
+
}
|
|
452
|
+
y -= paddingY;
|
|
453
|
+
};
|
|
454
|
+
// Render by block format
|
|
455
|
+
switch (block.format) {
|
|
456
|
+
case FormatTypes.HEADER_1:
|
|
457
|
+
case FormatTypes.HEADER_2:
|
|
458
|
+
case FormatTypes.HEADER_3:
|
|
459
|
+
case FormatTypes.HEADER_4:
|
|
460
|
+
case FormatTypes.HEADER_5:
|
|
461
|
+
case FormatTypes.HEADER_6: {
|
|
462
|
+
const align = block.metadata?.align;
|
|
463
|
+
drawHeading(String(block.content), align);
|
|
464
|
+
break;
|
|
465
|
+
}
|
|
466
|
+
case FormatTypes.BULLET: {
|
|
467
|
+
const items = Array.isArray(block.content) ? block.content.map(String) : [String(block.content)];
|
|
468
|
+
drawBulletList(items);
|
|
469
|
+
break;
|
|
470
|
+
}
|
|
471
|
+
case FormatTypes.NUMBERED: {
|
|
472
|
+
const items = Array.isArray(block.content) ? block.content.map(String) : [String(block.content)];
|
|
473
|
+
drawNumberedList(items);
|
|
474
|
+
break;
|
|
475
|
+
}
|
|
476
|
+
case FormatTypes.QUOTE: {
|
|
477
|
+
drawQuote(String(block.content));
|
|
478
|
+
break;
|
|
479
|
+
}
|
|
480
|
+
case FormatTypes.CODE_BLOCK: {
|
|
481
|
+
const lines = Array.isArray(block.content) ? block.content.map(String) : String(block.content).split('\n');
|
|
482
|
+
drawCodeBlock(lines);
|
|
483
|
+
break;
|
|
484
|
+
}
|
|
485
|
+
default: {
|
|
486
|
+
if (typeof block.content === 'string') {
|
|
487
|
+
drawParagraph(block.content);
|
|
488
|
+
}
|
|
489
|
+
else {
|
|
490
|
+
for (const c of block.content) {
|
|
491
|
+
drawParagraph(String(c));
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
console.log(`createPdf: Successfully processed block ${i + 1}`);
|
|
497
|
+
y -= paragraphSpacing;
|
|
498
|
+
lastLineHeight = lineHeight;
|
|
499
|
+
}
|
|
500
|
+
catch (blockError) {
|
|
501
|
+
console.error(`createPdf: Error processing block ${i + 1}:`, blockError);
|
|
502
|
+
throw blockError;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
console.log('createPdf: About to save PDF document');
|
|
506
|
+
const pdfBytes = await pdfDoc.save();
|
|
507
|
+
console.log('createPdf: PDF saved successfully, bytes length:', pdfBytes.length);
|
|
508
|
+
// writeFile('output.pdf', pdfBytes, () => {
|
|
509
|
+
// console.log('PDF created successfully') // Still only saves file, no API yet
|
|
510
|
+
// })
|
|
511
|
+
return pdfBytes;
|
|
512
|
+
}
|
|
513
|
+
catch (error) {
|
|
514
|
+
console.error('createPdf: Error during PDF creation:', error);
|
|
515
|
+
throw error;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export declare enum FormatTypes {
|
|
2
|
+
HEADER_1 = 0,
|
|
3
|
+
HEADER_2 = 1,
|
|
4
|
+
HEADER_3 = 2,
|
|
5
|
+
HEADER_4 = 3,
|
|
6
|
+
HEADER_5 = 4,
|
|
7
|
+
HEADER_6 = 5,
|
|
8
|
+
PARAGRAPH = 6,
|
|
9
|
+
BULLET = 7,
|
|
10
|
+
NUMBERED = 8,
|
|
11
|
+
TABLE = 9,
|
|
12
|
+
IMAGE = 10,
|
|
13
|
+
CODE_BLOCK = 11,
|
|
14
|
+
QUOTE = 12
|
|
15
|
+
}
|
|
16
|
+
export declare enum Fonts {
|
|
17
|
+
TIMES_ROMAN = 0,
|
|
18
|
+
COURIER = 1,
|
|
19
|
+
HELVETICA = 2,
|
|
20
|
+
HELVETICA_BOLD = 3,
|
|
21
|
+
HELVETICA_ITALIC = 4,
|
|
22
|
+
HELVETICA_BOLD_ITALIC = 5
|
|
23
|
+
}
|
|
24
|
+
export interface DocumentBlock {
|
|
25
|
+
format: FormatTypes;
|
|
26
|
+
content: string | string[];
|
|
27
|
+
metadata?: Record<string, any>;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=jsonStyles.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsonStyles.d.ts","sourceRoot":"","sources":["../../src/lib/jsonStyles.ts"],"names":[],"mappings":"AAGA,oBAAY,WAAW;IACnB,QAAQ,IAAA;IACR,QAAQ,IAAA;IACR,QAAQ,IAAA;IACR,QAAQ,IAAA;IACR,QAAQ,IAAA;IACR,QAAQ,IAAA;IACR,SAAS,IAAA;IACT,MAAM,IAAA;IACN,QAAQ,IAAA;IACR,KAAK,IAAA;IACL,KAAK,KAAA;IACL,UAAU,KAAA;IACV,KAAK,KAAA;CACR;AAGD,oBAAY,KAAK;IACb,WAAW,IAAA;IACX,OAAO,IAAA;IACP,SAAS,IAAA;IACT,cAAc,IAAA;IACd,gBAAgB,IAAA;IAChB,qBAAqB,IAAA;CACxB;AAID,MAAM,WAAW,aAAa;IAC1B,MAAM,EAAE,WAAW,CAAC;IACpB,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAClC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// -----------------------------
|
|
2
|
+
// Basic format enums and interfaces
|
|
3
|
+
// -----------------------------
|
|
4
|
+
export var FormatTypes;
|
|
5
|
+
(function (FormatTypes) {
|
|
6
|
+
FormatTypes[FormatTypes["HEADER_1"] = 0] = "HEADER_1";
|
|
7
|
+
FormatTypes[FormatTypes["HEADER_2"] = 1] = "HEADER_2";
|
|
8
|
+
FormatTypes[FormatTypes["HEADER_3"] = 2] = "HEADER_3";
|
|
9
|
+
FormatTypes[FormatTypes["HEADER_4"] = 3] = "HEADER_4";
|
|
10
|
+
FormatTypes[FormatTypes["HEADER_5"] = 4] = "HEADER_5";
|
|
11
|
+
FormatTypes[FormatTypes["HEADER_6"] = 5] = "HEADER_6";
|
|
12
|
+
FormatTypes[FormatTypes["PARAGRAPH"] = 6] = "PARAGRAPH";
|
|
13
|
+
FormatTypes[FormatTypes["BULLET"] = 7] = "BULLET";
|
|
14
|
+
FormatTypes[FormatTypes["NUMBERED"] = 8] = "NUMBERED";
|
|
15
|
+
FormatTypes[FormatTypes["TABLE"] = 9] = "TABLE";
|
|
16
|
+
FormatTypes[FormatTypes["IMAGE"] = 10] = "IMAGE";
|
|
17
|
+
FormatTypes[FormatTypes["CODE_BLOCK"] = 11] = "CODE_BLOCK";
|
|
18
|
+
FormatTypes[FormatTypes["QUOTE"] = 12] = "QUOTE";
|
|
19
|
+
})(FormatTypes || (FormatTypes = {}));
|
|
20
|
+
export var Fonts;
|
|
21
|
+
(function (Fonts) {
|
|
22
|
+
Fonts[Fonts["TIMES_ROMAN"] = 0] = "TIMES_ROMAN";
|
|
23
|
+
Fonts[Fonts["COURIER"] = 1] = "COURIER";
|
|
24
|
+
Fonts[Fonts["HELVETICA"] = 2] = "HELVETICA";
|
|
25
|
+
Fonts[Fonts["HELVETICA_BOLD"] = 3] = "HELVETICA_BOLD";
|
|
26
|
+
Fonts[Fonts["HELVETICA_ITALIC"] = 4] = "HELVETICA_ITALIC";
|
|
27
|
+
Fonts[Fonts["HELVETICA_BOLD_ITALIC"] = 5] = "HELVETICA_BOLD_ITALIC";
|
|
28
|
+
})(Fonts || (Fonts = {}));
|