Skip to content

Instantly share code, notes, and snippets.

@mopsicus
Created December 2, 2024 14:14
Show Gist options
  • Select an option

  • Save mopsicus/120f2e808cb22801a5c0ba2758775af1 to your computer and use it in GitHub Desktop.

Select an option

Save mopsicus/120f2e808cb22801a5c0ba2758775af1 to your computer and use it in GitHub Desktop.
Russian Lotto card generator. Generate any number of pages with 4 cards to play.
<!-- This code generated via ChatGPT by Mopsicus -->
<!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;
}
#controls {
margin-bottom: 20px;
}
input[type="number"] {
padding: 5px;
width: 100px;
}
</style>
</head>
<body>
<h1>Генератор карточек для Русского Лото</h1>
<div id="controls">
<label for="pageCount">Количество страниц: </label>
<input type="number" id="pageCount" min="1" value="1">
<button onclick="generatePDF()">Сгенерировать PDF</button>
</div>
<div id="cardContainer">
<!-- Генерация карточек будет отображаться здесь -->
</div>
<script>
// Функция для генерации одной карточки
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(num => num !== 0).length === 5);
if (isValid) {
return tempCard;
} else {
return generateValidCard();
}
};
return generateValidCard();
};
// Генерация PDF с заданным количеством страниц
const generatePDF = async () => {
const pageCount = parseInt(document.getElementById('pageCount').value);
const { PDFDocument } = PDFLib;
const pdfDoc = await PDFDocument.create();
const pageWidth = 595; // Размер страницы A4 в точках
const pageHeight = 842;
// Встраиваем шрифт Helvetica
const font = await pdfDoc.embedFont(PDFLib.StandardFonts.Helvetica);
const fontSize = 20; // Увеличенный размер шрифта для чисел
// Цвет для текста
const blackColor = PDFLib.rgb(0, 0, 0);
// Расстояние между карточками
const verticalSpacing = 20; // Увеличенное расстояние между карточками по вертикали
for (let i = 0; i < pageCount; i++) {
const page = pdfDoc.addPage([pageWidth, pageHeight]);
// Вставка надписи, строго по центру
const title = 'LOTO';
const titleWidth = font.widthOfTextAtSize(title, 20);
page.drawText(title, {
x: (pageWidth - titleWidth) / 2, // Центрирование по X
y: pageHeight - 50,
size: 20,
font,
color: blackColor,
});
// Генерация и отрисовка карточек
const cardWidth = pageWidth - 20; // Карточки занимают всю ширину
const cardHeight = 150; // Высота карточки
const cardsPerColumn = Math.floor((pageHeight - 100) / (cardHeight + verticalSpacing)); // Количество карточек по вертикали
for (let row = 0; row < cardsPerColumn; row++) {
const card = generateLotoCard();
const cardY = pageHeight - (row + 1) * (cardHeight + verticalSpacing) - 70;
const cardX = 10; // Расположение карточки по горизонтали
// Рисуем рамку карточки (толщина рамки 3 пикселя)
page.drawRectangle({
x: cardX,
y: cardY,
width: cardWidth,
height: cardHeight,
borderColor: blackColor,
borderWidth: 3, // Устанавливаем толщину рамки 3 пикселя
});
// Рисуем числа в карточке
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 x = cardX + colIndex * cellWidth + (cellWidth / 2) - 12; // Центрирование по X
const y = cardY + (2 - rowIndex) * cellHeight + (cellHeight / 2) - 10; // Центрирование по Y
// Рисуем рамку для числа (включая пустые ячейки)
page.drawRectangle({
x: cardX + colIndex * cellWidth,
y: cardY + (2 - rowIndex) * cellHeight,
width: cellWidth,
height: cellHeight,
borderColor: blackColor,
borderWidth: 1,
});
// Если число присутствует, отображаем его в центре ячейки
if (num !== 0) {
page.drawText(num.toString(), {
x,
y,
size: fontSize, // Увеличенный размер шрифта
font,
color: blackColor,
align: 'center',
baseline: 'middle',
});
}
}
}
}
}
const pdfBytes = await pdfDoc.save();
// Создаем ссылку для скачивания
const link = document.createElement('a');
link.href = URL.createObjectURL(new Blob([pdfBytes], { type: 'application/pdf' }));
link.download = 'loto_cards.pdf';
link.click();
};
</script>
</body>
</html>
@rozhdestvofrolov-hash
Copy link

не работает выгрузка в pdf

@rozhdestvofrolov-hash
Copy link

но фалй работает)

@mopsicus
Copy link
Author

mopsicus commented Oct 1, 2025

Только что проверил в сафари и яндекс браузере, работает, пдфка сохраняется

@mapkepp
Copy link

mapkepp commented Feb 11, 2026

пишу первый раз сильно не пинайте, немного поковырял код через нейронку яндекса и вроде получилось что то

<!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