Skip to content

Instantly share code, notes, and snippets.

@mapkepp
Last active February 11, 2026 01:22
Show Gist options
  • Select an option

  • Save mapkepp/30f6823c12f5131505cfe37e013e6fe5 to your computer and use it in GitHub Desktop.

Select an option

Save mapkepp/30f6823c12f5131505cfe37e013e6fe5 to your computer and use it in GitHub Desktop.
Генератор карточек лото
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Генератор карточек для Русского Лотто</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf-lib/1.17.1/pdf-lib.min.js"></script>
<style>
body {
font-family: Arial, sans-serif;
text-align: center;
padding: 20px;
margin: 0;
}
#controls {
margin-bottom: 20px;
display: flex;
flex-direction: column;
align-items: center;
gap: 15px;
}
.control-group {
display: flex;
align-items: center;
gap: 10px;
width: 100%;
max-width: 600px;
justify-content: flex-start;
}
label {
font-weight: bold;
white-space: nowrap;
min-width: 200px;
text-align: left;
}
input[type="number"] {
padding: 5px;
width: 80px;
}
button {
margin-top: 15px;
padding: 10px 20px;
font-size: 16px;
cursor: pointer;
}
</style>
</head>
<body>
<h1>Генератор карточек для Русского Лотто</h1>
<div id="controls">
<div class="control-group">
<label for="pageCount">Количество страниц кратно 3м (на странице по 4 карточки):</label>
<input type="number" id="pageCount" min="1" value="6">
</div>
<div class="control-group">
<label for="fontFamily">Шрифт чисел:</label>
<select id="fontFamily">
<option value="Helvetica" selected>Helvetica (обычный)</option>
<option value="HelveticaBold">Helvetica Bold (жирный)</option>
<option value="Helvetica-Oblique">Helvetica Oblique (курсив)</option>
<option value="Helvetica-BoldOblique">Helvetica Bold Oblique</option>
</select>
</div>
<div class="control-group">
<label for="fontSize">Размер шрифта чисел (pt):</label>
<input type="number" id="fontSize" min="16" max="36" value="30">
</div>
<div class="control-group">
<label for="outerBorder">Толщина внешней рамки (px):</label>
<input type="number" id="outerBorder" min="1" max="10" value="4">
</div>
<div class="control-group">
<label for="innerBorder">Толщина внутренних линий (px):</label>
<input type="number" id="innerBorder" min="1" max="5" value="2">
</div>
<!-- Ввод ширины в десятых мм -->
<div class="control-group">
<label for="cardWidthTenths">Ширина карточки (десятые мм) стандарт 2200:</label>
<input type="number" id="cardWidthTenths" min="1060" max="2120" value="1965">
</div>
<!-- Ввод высоты в десятых мм -->
<div class="control-group">
<label for="cardHeightTenths">Высота карточки (десятые мм) стандарт 800:</label>
<input type="number" id="cardHeightTenths" min="350" max="1060" value="657">
</div>
<div class="control-group">
<label for="verticalSpacing">Расстояние между карточками (pt):</label>
<input type="number" id="verticalSpacing" min="7" max="150" value="20">
</div>
<!-- Размер шрифта метки -->
<div class="control-group">
<label for="dateTimeFontSize">Размер шрифта даты‑времени (pt):</label>
<input type="number" id="dateTimeFontSize" min="1" max="20" value="3">
</div>
<div class="control-group">
<label for="numberFontSize">Размер шрифта номера (pt):</label>
<input type="number" id="numberFontSize" min="1" max="20" value="4">
</div>
<!-- Расстояние метки от рамки -->
<div class="control-group">
<label for="footerMargin">Расстояние метки от рамки (pt):</label>
<input type="number" id="footerMargin" min="-50" max="50" value="5">
</div>
<button onclick="generatePDF()">Сгенерировать PDF</button>
</div>
<div id="cardContainer">
<!-- Карточки будут отображаться здесь -->
</div>
<script>
// Генерация карточки лото (9×3, 15 чисел)
const generateLotoCard = () => {
const card = [Array(9).fill(0), Array(9).fill(0), Array(9).fill(0)];
const columnRanges = [
[1, 9], [10, 19], [20, 29], [30, 39], [40, 49],
[50, 59], [60, 69], [70, 79], [80, 90]
];
const generateValidCard = () => {
const tempCard = [Array(9).fill(0), Array(9).fill(0), Array(9).fill(0)];
for (let col = 0; col < 9; col++) {
const [min, max] = columnRanges[col];
const numbersInColumn = Array.from(
{ length: max - min + 1 },
(_, i) => i + min
);
const numCount = Math.floor(Math.random() * 2) + 1;
const selectedNumbers = [];
for (let i = 0; i < numCount; i++) {
const randomIndex = Math.floor(Math.random() * numbersInColumn.length);
const num = numbersInColumn[randomIndex];
selectedNumbers.push(num);
numbersInColumn.splice(randomIndex, 1);
}
for (const num of selectedNumbers) {
let row;
do {
row = Math.floor(Math.random() * 3);
} while (tempCard[row][col] !== 0);
tempCard[row][col] = num;
}
}
const isValid = tempCard.every(row => row.filter(n => n !== 0).length === 5);
return isValid ? tempCard : generateValidCard();
};
return generateValidCard();
};
// Генерация PDF
const generatePDF = async () => {
try {
const pageCount = parseInt(document.getElementById('pageCount').value);
const fontSize = parseInt(document.getElementById('fontSize').value);
const outerBorder = parseInt(document.getElementById('outerBorder').value);
const innerBorder = parseInt(document.getElementById('innerBorder').value);
// Конвертация: десятые мм → pt (1 десятая мм = 0.283464567 pt)
const tenthsToPt = 0.283464567;
// Расчёт высоты карточки в pt
const cardHeightInput = parseInt(document.getElementById('cardHeightTenths').value);
const cardHeight = Math.round(cardHeightInput * tenthsToPt);
// Расчёт ширины карточки в pt
const cardWidthInput = parseInt(document.getElementById('cardWidthTenths').value);
const cardWidth = Math.round(cardWidthInput * tenthsToPt);
const verticalSpacing = parseInt(document.getElementById('verticalSpacing').value);
// Размеры шрифтов для метки
const dateTimeFontSize = parseInt(document.getElementById('dateTimeFontSize').value);
const numberFontSize = parseInt(document.getElementById('numberFontSize').value);
// Расстояние от нижней рамки карточки до метки (в pt)
const footerMargin = parseInt(document.getElementById('footerMargin').value);
const { PDFDocument } = PDFLib;
const pdfDoc = await PDFDocument.create();
const pageWidth = 595; // A4 ширина в pt
const pageHeight = 842; // A4 высота в pt
// Считываем выбранный шрифт (как раньше)
const fontFamily = document.getElementById('fontFamily').value;
let font;
switch (fontFamily) {
case 'Helvetica':
font = await pdfDoc.embedFont(PDFLib.StandardFonts.Helvetica);
break;
case 'HelveticaBold':
font = await pdfDoc.embedFont(PDFLib.StandardFonts.HelveticaBold);
break;
case 'Helvetica-Oblique':
font = await pdfDoc.embedFont(PDFLib.StandardFonts['Helvetica-Oblique']);
break;
case 'Helvetica-BoldOblique':
font = await pdfDoc.embedFont(PDFLib.StandardFonts['Helvetica-BoldOblique']);
break;
default:
font = await pdfDoc.embedFont(PDFLib.StandardFonts.Helvetica);
}
const blackColor = PDFLib.rgb(0, 0, 0);
// !!! ВАЖНО: счётчик теперь ВНЕ цикла по страницам
let globalCardCounter = 1;
for (let i = 0; i < pageCount; i++) {
const page = pdfDoc.addPage([pageWidth, pageHeight]);
// Расчёт количества карточек на странице по вертикали
const cardsPerColumn = Math.floor((pageHeight - 0) / (cardHeight + verticalSpacing));
for (let row = 0; row < cardsPerColumn; row++) {
const card = generateLotoCard();
const cardY = pageHeight - (row + 1) * (cardHeight + verticalSpacing) - 0;
const cardX = (pageWidth - cardWidth) / 2;
// Внешняя рамка карточки (как раньше)
page.drawRectangle({
x: cardX,
y: cardY,
width: cardWidth,
height: cardHeight,
borderColor: blackColor,
borderWidth: outerBorder,
});
const cellWidth = cardWidth / 9;
const cellHeight = cardHeight / 3;
// Рисуем ячейки и числа (как раньше)
for (let rowIndex = 0; rowIndex < 3; rowIndex++) {
for (let colIndex = 0; colIndex < 9; colIndex++) {
const num = card[rowIndex][colIndex];
const xCenter = cardX + colIndex * cellWidth + cellWidth / 2;
const yCenter = cardY + (2 - rowIndex) * cellHeight + cellHeight / 2;
page.drawRectangle({
x: cardX + colIndex * cellWidth,
y: cardY + (2 - rowIndex) * cellHeight,
width: cellWidth,
height: cellHeight,
borderColor: blackColor,
borderWidth: innerBorder,
});
if (num !== 0) {
const text = num.toString();
const textWidth = font.widthOfTextAtSize(text, fontSize);
const x = xCenter - textWidth / 2;
let k;
if (fontSize <= 22) {
k = 0.35;
} else if (fontSize <= 28) {
k = 0.35 - (fontSize - 22) * (0.05 / 6);
} else {
k = 0.30 - (fontSize - 28) * (0.05 / 8);
}
const y = yCenter - fontSize * k;
page.drawText(text, {
x: x,
y: y,
size: fontSize,
font: font,
color: blackColor,
});
}
}
}
// --- МЕТКА ВНИЗУ КАРТОЧКИ ---
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const seconds = String(now.getSeconds()).padStart(2, '0');
const milliseconds = String(now.getMilliseconds()).padStart(3, '0');
const dateTimeStr = `${year}${month}${day}${hours}${minutes}${seconds}${milliseconds}`;
const numberStr = `${globalCardCounter}`; // Используем общий счётчик
const footerY = cardY + footerMargin;
// Отрисовка даты-времени
const dateTimeTextWidth = font.widthOfTextAtSize(dateTimeStr, dateTimeFontSize);
const dateTimeX = cardX + (cardWidth - dateTimeTextWidth - 5) / 2;
page.drawText(dateTimeStr, {
x: dateTimeX,
y: footerY,
size: dateTimeFontSize,
font: font,
color: blackColor,
});
// Отрисовка номера
const numberTextWidth = font.widthOfTextAtSize(numberStr, numberFontSize);
const numberX = dateTimeX + dateTimeTextWidth + 3;
page.drawText(numberStr, {
x: numberX,
y: footerY,
size: numberFontSize,
font: font,
color: blackColor,
});
globalCardCounter++; // Увеличиваем счётчик ПОСЛЕ отрисовки карточки
}
}
// Сохранение и скачивание PDF
const pdfBytes = await pdfDoc.save();
const blob = new Blob([pdfBytes], { type: 'application/pdf' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'loto_cards.pdf';
link.style.display = 'none';
document.body.appendChild(link);
link.click();
// Очистка: удаление временного URL и ссылки
URL.revokeObjectURL(url);
document.body.removeChild(link);
} catch (error) {
console.error('Ошибка при генерации PDF:', error);
alert('Произошла ошибка при создании PDF. Проверьте консоль для деталей.');
}
};
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment