Compare commits

..

No commits in common. "main" and "v1.0.0" have entirely different histories.
main ... v1.0.0

8 changed files with 134 additions and 111 deletions

1
.gitignore vendored
View File

@ -1 +0,0 @@
*.pdf

View File

@ -1,15 +1,12 @@
# Calendar Generator PDF App # Calendar Generator Flask App
This python application generates a pdf-file based calendar with images and associated texts for each day. It's designed to read images and texts from specified directories and display them in a calendar format. Per DIN A4 page 4 days get displayed.
This Flask application generates a web-based calendar view with images and associated texts for each day. It's designed to read images and texts from specified directories and display them in a calendar format. The web-based pages can be printed to a pdf-file. This is easy, because every browser application has the option to print a page to a pdf-file. Per DIN A4 page 4 days get displayed.
## Setup ## Setup
### Prerequisites ### Prerequisites
- Python 3.x - Python 3.x
- Flask
- Basic understanding of Python and Flask - Basic understanding of Python and Flask
- reportlab (python modul)
- os (python modul)
- re (python modul)
- textwrap (python modul)
### Installation ### Installation
@ -19,9 +16,9 @@ This python application generates a pdf-file based calendar with images and asso
2. **Install Required Packages** 2. **Install Required Packages**
reportlab is required and can be installed via pip: Flask is required and can be installed via pip:
```bash ```bash
pip install reportlab pip install Flask
`````` ``````
### Folder Structure ### Folder Structure
@ -33,18 +30,19 @@ project_folder/
│ app.py │ app.py
│ README.md │ README.md
├───images/ ├───static/
│ │ 2024-01-15.jpg │ └───images/
│ │ 2024-01-16.png │ │ 2024-01-15.jpg
│ ... │ │ 2024-01-16.png
│ ...
└───texts/ └───texts/
│ 2024-01-15.txt │ 2024-01-15.txt
│ 2024-01-16.txt │ 2024-01-16.txt
... ...
``` ```
- `app.py`: Main python application file. - `app.py`: Main Flask application file.
- `images/`: Directory for storing calendar images. Images should be named `YYYY-MM-DD.extension`. - `static/images/`: Directory for storing calendar images. Images should be named `YYYY-MM-DD.extension`.
- `texts/`: Directory for storing text files corresponding to each day, named `YYYY-MM-DD.txt`. - `texts/`: Directory for storing text files corresponding to each day, named `YYYY-MM-DD.txt`.
### Running the Application ### Running the Application
@ -54,11 +52,13 @@ project_folder/
```bash ```bash
python app.py python app.py
`````` ``````
3. "PDF created successfully" shoud appear, then you can access the pdf file which is in the same directory as the script. 3. Access the calendar in a web browser at `http://127.0.0.1:5000/`.
## How It Works ## How It Works
- The application serves a web page displaying a calendar.
- Each day on the calendar can have an image and a text associated with it. - Each day on the calendar can have an image and a text associated with it.
- Images are read from `images/`, and text files are from `texts/`. - Images are read from `static/images/`, and text files are from `texts/`.
- Files are matched based on their filenames, formatted as `YYYY-MM-DD`. - Files are matched based on their filenames, formatted as `YYYY-MM-DD`.
- The calendar is dynamically generated based on available images and texts. - The calendar is dynamically generated based on available images and texts.
- If an image or text is not available for a specific day, it will be displayed without that content. - If an image or text is not available for a specific day, it will be displayed without that content.
@ -66,6 +66,7 @@ project_folder/
## Notes ## Notes
- Text for each day should be concise. Lengthy texts may be truncated in the display to maintain the layout of the calendar. - Text for each day should be concise. Lengthy texts may be truncated in the display to maintain the layout of the calendar.
- You can customize the appearance and behavior of the calendar by modifying the HTML, CSS, and Flask application logic.
## Copyright and license ## Copyright and license
This code is for my very good friend Felix W.. Code copyright 2024 Lorenz B. This code is for my very good friend Felix W.. Code copyright 2024 Lorenz B.

97
app.py
View File

@ -1,23 +1,9 @@
from reportlab.lib.pagesizes import A4 from flask import Flask, render_template
from reportlab.lib import colors
from reportlab.pdfgen import canvas
from reportlab.lib.units import mm
from datetime import datetime from datetime import datetime
import os import os
import re import re
import textwrap
# Konstanten für die Abmessungen app = Flask(__name__)
CIRCLE_RADIUS = 3 * mm
CIRCLE_DISTANCE = 80 * mm
TOP_MARGIN_TO_CENTER = 12 * mm
IMAGE_TOP_MARGIN = 28 * mm
IMAGE_HEIGHT = 60 * mm
TEXT_MARGIN = 5 * mm
LEFT_MARGIN_TO_CENTER = (A4[0] / 4) - (CIRCLE_DISTANCE / 2) # Zentriert zwischen den Tagen
IMAGE_SHIFT_UP = 1 * mm # Bild nach oben verschieben
DATE_SHIFT_DOWN = 8 * mm # Datum nach unten verschieben
TEXT_SHIFT_DOWN = 8 * mm # Text nach unten verschieben
def get_dates_from_images(image_directory): def get_dates_from_images(image_directory):
# Regular expression to match files with a date format YYYY-MM-DD # Regular expression to match files with a date format YYYY-MM-DD
@ -64,81 +50,18 @@ def get_image_and_text_for_date(date, image_directory, text_directory):
return image_file, text_content return image_file, text_content
def draw_circle(c, x, y):
c.circle(x, y, CIRCLE_RADIUS, stroke=1, fill=0)
def create_pdf(pages_data, output_filename): @app.route('/')
c = canvas.Canvas(output_filename, pagesize=A4) def calendar():
c.setCreator('Lorenz B.')
c.setTitle('Calendar')
c.setAuthor('Lorenz B.')
c.setSubject('calendar generator')
width, height = A4
max_text_width = (width / 2) - (2 * TEXT_MARGIN)
DATE_FONT_SIZE = 20
DATE_FONT = "Helvetica-Bold"
for page in pages_data:
for index, day in enumerate(page):
c.setDash(1, 0)
c.setStrokeColor(colors.black)
# Berechnung der Positionen für jeden Tag
x = (index % 2) * (width / 2)
y = height - (index // 2 + 1) * (height / 2)
# Höhere Position für das Bild
image_y_position = y + (height / 2 - IMAGE_TOP_MARGIN - IMAGE_HEIGHT) + IMAGE_SHIFT_UP
# Kreise zeichnen
draw_circle(c, x + LEFT_MARGIN_TO_CENTER, y + height / 2 - TOP_MARGIN_TO_CENTER)
draw_circle(c, x + width / 2 - LEFT_MARGIN_TO_CENTER, y + height / 2 - TOP_MARGIN_TO_CENTER)
# Bild einfügen
if day['image_file']:
c.drawImage(day['image_file'], x, image_y_position, width=width / 2, height=IMAGE_HEIGHT, preserveAspectRatio=True, anchor='n')
# Datum direkt unter dem Bild einfügen
date_x_position = x + (width / 4) # Zentrum des Tagesbereichs
date_y_position = image_y_position - DATE_SHIFT_DOWN
date_str = day['date'].strftime('%A, %d.%m.%Y')
c.setFont(DATE_FONT, DATE_FONT_SIZE) # Schriftart und Schriftgröße setzen
date_width = c.stringWidth(date_str, DATE_FONT, DATE_FONT_SIZE)
c.drawString(date_x_position - (date_width / 2), date_y_position, date_str)
# Text unter dem Datum einfügen
text_y_position = date_y_position - TEXT_SHIFT_DOWN - 5 * mm # Extra Abstand nach einem größeren Datum
if day['text_content']:
c.setFont("Helvetica", 12) # Schriftart zurücksetzen für den Text
wrapped_text = textwrap.fill(day['text_content'], width=50) # Anpassen für die passende Zeilenlänge
text = c.beginText(x + TEXT_MARGIN, text_y_position)
for line in wrapped_text.split('\n'):
text.textLine(line)
c.drawText(text)
# Gestrichelte Linien zeichnen, falls notwendig
c.setDash(1, 2)
c.setStrokeColor(colors.grey)
if index % 2 == 0: # Vertikale Linie rechts für den Tag
c.line(x + width / 2, y, x + width / 2, y + height / 2)
if index < 2: # Horizontale Linie unten für den Tag
c.line(x, y, x + width / 2, y)
c.showPage()
c.save()
def main():
BASE_DIR = os.path.dirname(os.path.abspath(__file__)) BASE_DIR = os.path.dirname(os.path.abspath(__file__))
image_directory = os.path.join(BASE_DIR, 'images') image_directory = os.path.join(BASE_DIR, 'static', 'images')
text_directory = os.path.join(BASE_DIR, 'texts') text_directory = os.path.join(BASE_DIR, 'texts')
# Get sorted dates from image files # Get sorted dates from image files
dates = get_dates_from_images(image_directory) dates = get_dates_from_images(image_directory)
if not dates: if not dates:
print("No images found with the correct date format in the name.") return "No images found with the correct date format in the name.", 404
return
# Generate data for each date # Generate data for each date
pages_data = [] pages_data = []
@ -160,11 +83,7 @@ def main():
if days_data: if days_data:
pages_data.append(days_data) pages_data.append(days_data)
# Create the PDF return render_template('index.html', pages=pages_data)
output_filename = os.path.join(BASE_DIR, 'calendar.pdf')
create_pdf(pages_data, output_filename)
print("PDF created successfully.")
if __name__ == '__main__': if __name__ == '__main__':
main() app.run(debug=True)

3
images/.gitignore vendored
View File

@ -1,3 +0,0 @@
*.png
*.jpeg
*.jpg

BIN
static/images/2024-01-15.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 KiB

107
templates/index.html Normal file
View File

@ -0,0 +1,107 @@
<!DOCTYPE html>
<html>
<head>
<style>
@media print,
screen {
body {
width: 210mm;
height: 297mm;
margin: 0mm 0mm 0mm 0mm;
padding: 0;
}
.page {
width: 210mm;
height: 297mm;
display: flex;
flex-wrap: wrap;
page-break-after: always;
}
.day {
width: 50%;
height: 50%;
box-sizing: border-box;
position: relative;
}
.circle {
width: 10mm;
height: 10mm;
position: absolute;
border-radius: 50%;
border: 1px solid black;
background-color: transparent;
}
.circle.top-left {
top: 10mm;
left: 10mm;
}
.circle.top-right {
top: 10mm;
right: 10mm;
}
.image-container {
margin-top: 28mm;
text-align: center;
height: 60mm;
}
.image-container img {
max-width: 100%;
max-height: 60mm;
height: auto;
width: auto;
object-fit: contain;
}
.date-info {
text-align: center;
font-family: Arial, Helvetica, sans-serif;
}
.text {
padding-left: 5mm;
padding-right: 5mm;
font-family: 'Courier New', Courier, monospace;
color: #666666;
}
}
</style>
</head>
<body>
{% for page in pages %}
<div class="page">
{% for day in page %}
<div class="day" style="{{ 'border-right: 1px dashed grey; border-bottom: 1px dashed grey;' if loop.index0 == 0 else '' }}
{{ 'border-bottom: 1px dashed grey;' if loop.index0 == 1 else '' }}
{{ 'border-right: 1px dashed grey;' if loop.index0 == 2 else '' }}">
<div class="circle top-left"></div>
<div class="circle top-right"></div>
<div class="image-container">
{% if day.image_file %}
<img src="{{ url_for('static', filename=day.image_file) }}" alt="Bild für {{ day.date.strftime('%d. %B %Y') }}" />
{% endif %}
</div>
<div class="date-info">
<h1>{{ day.date.strftime('%A %d.%m.%Y') }}</h1>
</div>
<div class="text">
{% if day.text_content %}
<p>{{ day.text_content }}</p>
{% endif %}
</div>
</div>
{% endfor %}
</div>
{% endfor %}
</body>
</html>

1
texts/.gitignore vendored
View File

@ -1 +0,0 @@
*.txt

1
texts/2024-01-15.txt Executable file
View File

@ -0,0 +1 @@
Beispiel Text