Compare commits

...

4 Commits
v1.0.0 ... main

8 changed files with 111 additions and 134 deletions

1
.gitignore vendored Normal file
View File

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

View File

@ -1,12 +1,15 @@
# Calendar Generator Flask App # Calendar Generator PDF 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
@ -16,9 +19,9 @@ This Flask application generates a web-based calendar view with images and assoc
2. **Install Required Packages** 2. **Install Required Packages**
Flask is required and can be installed via pip: reportlab is required and can be installed via pip:
```bash ```bash
pip install Flask pip install reportlab
`````` ``````
### Folder Structure ### Folder Structure
@ -30,19 +33,18 @@ project_folder/
│ app.py │ app.py
│ README.md │ README.md
├───static/ ├───images/
│ └───images/ │ │ 2024-01-15.jpg
│ │ 2024-01-15.jpg │ │ 2024-01-16.png
│ │ 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 Flask application file. - `app.py`: Main python application file.
- `static/images/`: Directory for storing calendar images. Images should be named `YYYY-MM-DD.extension`. - `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
@ -52,13 +54,11 @@ project_folder/
```bash ```bash
python app.py python app.py
`````` ``````
3. Access the calendar in a web browser at `http://127.0.0.1:5000/`. 3. "PDF created successfully" shoud appear, then you can access the pdf file which is in the same directory as the script.
## 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 `static/images/`, and text files are from `texts/`. - Images are read from `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,7 +66,6 @@ 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,9 +1,23 @@
from flask import Flask, render_template from reportlab.lib.pagesizes import A4
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
app = Flask(__name__) # Konstanten für die Abmessungen
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
@ -50,18 +64,81 @@ 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)
@app.route('/') def create_pdf(pages_data, output_filename):
def calendar(): c = canvas.Canvas(output_filename, pagesize=A4)
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, 'static', 'images') image_directory = os.path.join(BASE_DIR, '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:
return "No images found with the correct date format in the name.", 404 print("No images found with the correct date format in the name.")
return
# Generate data for each date # Generate data for each date
pages_data = [] pages_data = []
@ -83,7 +160,11 @@ def calendar():
if days_data: if days_data:
pages_data.append(days_data) pages_data.append(days_data)
return render_template('index.html', pages=pages_data) # Create the PDF
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__':
app.run(debug=True) main()

3
images/.gitignore vendored Normal file
View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 209 KiB

View File

@ -1,107 +0,0 @@
<!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 Normal file
View File

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

View File

@ -1 +0,0 @@
Beispiel Text