commit 2f0154b20f43d7e12ed593e0b3eda6212934fd59 Author: Michael Schapira - krilin Date: Wed Nov 12 21:40:44 2025 -0500 initial commit diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ee79ec6 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,70 @@ +# QR Code Generator - Dockerfile +# Multi-stage build for optimized container size + +FROM python:3.11-slim as builder + +# Set working directory +WORKDIR /app + +# Install system dependencies for Pillow and cairosvg +RUN apt-get update && apt-get install -y --no-install-recommends \ + gcc \ + libjpeg-dev \ + zlib1g-dev \ + libcairo2-dev \ + && rm -rf /var/lib/apt/lists/* + +# Copy requirements file +COPY requirements.txt . + +# Install Python dependencies +RUN pip install --no-cache-dir --user -r requirements.txt + + +# Final stage +FROM python:3.11-slim + +# Set working directory +WORKDIR /app + +# Install runtime dependencies +RUN apt-get update && apt-get install -y --no-install-recommends \ + libcairo2 \ + libjpeg62-turbo \ + && rm -rf /var/lib/apt/lists/* + +# Copy Python dependencies from builder +COPY --from=builder /root/.local /root/.local + +# Copy application code +COPY app.py . +COPY utils/ ./utils/ + +# Create directory for Streamlit config +RUN mkdir -p /root/.streamlit + +# Configure Streamlit +RUN echo '\ +[server]\n\ +headless = true\n\ +address = "0.0.0.0"\n\ +port = 8501\n\ +enableCORS = false\n\ +enableXsrfProtection = true\n\ +\n\ +[browser]\n\ +gatherUsageStats = false\n\ +' > /root/.streamlit/config.toml + +# Update PATH to include local Python packages +ENV PATH=/root/.local/bin:$PATH + +# Expose Streamlit port +EXPOSE 8501 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD python -c "import requests; requests.get('http://localhost:8501/_stcore/health')" + +# Run the application +ENTRYPOINT ["streamlit", "run", "app.py", "--server.port=8501"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..3b22ece --- /dev/null +++ b/README.md @@ -0,0 +1,301 @@ +# QR Code Generator + +A professional web application for generating QR codes with optional logo overlay and export capabilities in multiple formats and resolutions. + +## Features + +- **Dual QR Code Types**: + - **URL QR Codes**: Convert any URL into a scannable QR code + - **WiFi QR Codes**: Generate QR codes for WiFi network credentials (WPA/WPA2, WEP, or Open networks) +- **Logo Overlay**: Add your company logo or custom image to the center of the QR code +- **Multiple Export Formats**: + - PNG (Portable Network Graphics) + - JPEG (Joint Photographic Experts Group) + - SVG (Scalable Vector Graphics - perfect for print) +- **Flexible Resolution Options**: Export in various resolutions from 500px to 4000px +- **Customizable Appearance**: Choose QR code and background colors +- **Error Correction Levels**: Support for L, M, Q, and H error correction levels +- **WiFi Network Support**: Supports WPA/WPA2, WEP, and open networks with hidden network option +- **Responsive UI**: Clean, intuitive interface built with Streamlit +- **Containerized Deployment**: Ready-to-use Podman/Docker configuration + +## Tech Stack + +- **Python 3.11**: Core programming language +- **Streamlit**: Web application framework +- **qrcode**: QR code generation library +- **segno**: Advanced QR code library with SVG support +- **Pillow (PIL)**: Image processing +- **CairoSVG**: SVG rendering +- **Podman/Docker**: Containerization + +## Prerequisites + +### Local Installation +- Python 3.11 or higher +- pip (Python package manager) + +### Container Installation +- Podman or Docker installed on your system + +## Installation + +### Option 1: Local Development + +1. Clone the repository or download the files + +2. Create a virtual environment (recommended): +```bash +python -m venv venv +source venv/bin/activate # On Windows: venv\Scripts\activate +``` + +3. Install dependencies: +```bash +pip install -r requirements.txt +``` + +4. Run the application: +```bash +streamlit run app.py +``` + +5. Open your browser to `http://localhost:8501` + +### Option 2: Podman Container + +1. Build and run using the provided script: +```bash +./run-podman.sh +``` + +2. Or manually: +```bash +# Build the image +podman build -t qr-code-generator . + +# Run the container +podman run -d --name qr-app -p 8501:8501 qr-code-generator +``` + +3. Access the application at `http://localhost:8501` + +### Option 3: Docker Container + +Replace `podman` with `docker` in the commands above: +```bash +docker build -t qr-code-generator . +docker run -d --name qr-app -p 8501:8501 qr-code-generator +``` + +## Usage Guide + +### URL QR Code Generation + +1. **Select Mode**: Choose "URL" from the QR Code Type options +2. **Enter URL**: Type or paste the URL you want to encode +3. **Configure Settings** (optional): + - Adjust error correction level (use H for logos) + - Customize colors + - Select export format and resolution +4. **Generate**: Click the "Generate QR Code" button +5. **Download**: Use the download button to save your QR code + +### WiFi QR Code Generation + +1. **Select Mode**: Choose "WiFi" from the QR Code Type options +2. **Enter Network Details**: + - **Network Name (SSID)**: Your WiFi network name + - **Password**: Your WiFi password (leave empty for open networks) + - **Security Type**: Select WPA/WPA2, WEP, or None (Open) + - **Hidden Network**: Check if your network is hidden +3. **Configure Settings** (optional): + - Adjust error correction level + - Customize colors + - Select export format and resolution +4. **Generate**: Click the "Generate QR Code" button +5. **Share**: Users can scan the QR code to automatically connect to your WiFi network + +**Note**: WiFi QR codes work on most modern smartphones (iOS 11+, Android 10+). The device will automatically detect the QR code contains WiFi credentials and prompt to connect. + +### Adding a Logo + +1. Follow steps 1-2 from basic generation +2. **Upload Logo**: Click "Upload logo image" and select your image file +3. **Adjust Size**: Use the slider to set the logo size ratio (0.1 - 0.4) +4. **Generate**: Click "Generate QR Code" +5. **Download**: Save your branded QR code + +### Error Correction Levels + +- **L (7%)**: Suitable for clean environments, smallest QR code +- **M (15%)**: Default level, balanced +- **Q (25%)**: Better damage resistance +- **H (30%)**: Recommended for QR codes with logos, highest damage resistance + +### Format Selection + +- **PNG**: Best for web use, supports transparency +- **JPEG**: Smaller file size, good for photos, no transparency +- **SVG**: Vector format, infinitely scalable, perfect for print + +## Project Structure + +``` +qr-code-generator/ +├── app.py # Main Streamlit application +├── utils/ +│ ├── __init__.py # Package initializer +│ ├── qr_generator.py # QR code generation utilities +│ └── wifi_qr.py # WiFi QR code utilities +├── requirements.txt # Python dependencies +├── Dockerfile # Container definition +├── .dockerignore # Docker ignore rules +├── run-podman.sh # Podman run script +├── .gitignore # Git ignore rules +└── README.md # This file +``` + +## Configuration Options + +### Streamlit Configuration + +The application uses default Streamlit settings. To customize: + +Create `.streamlit/config.toml`: +```toml +[server] +port = 8501 +headless = true + +[theme] +primaryColor = "#FF4B4B" +backgroundColor = "#FFFFFF" +secondaryBackgroundColor = "#F0F2F6" +textColor = "#262730" +font = "sans serif" +``` + +### Environment Variables + +For containerized deployment, you can customize: +- `STREAMLIT_SERVER_PORT`: Change the port (default: 8501) +- `STREAMLIT_BROWSER_GATHER_USAGE_STATS`: Disable telemetry + +## Development + +### Code Style + +This project follows Python best practices: +- PEP 8 style guide +- Type hints for function parameters +- Comprehensive docstrings +- Modular, reusable code structure + +### Adding New Features + +1. **New Export Format**: Update `utils/qr_generator.py` and add format handler +2. **UI Enhancements**: Modify `app.py` Streamlit components +3. **New QR Styles**: Extend `QRCodeGenerator` class methods + +### Testing + +Run manual tests by generating QR codes with various configurations: +- Different URLs (short, long, special characters) +- With and without logos +- All export formats +- Various resolutions +- Different error correction levels + +## Container Management + +### Podman Commands + +```bash +# View logs +podman logs qr-app + +# Stop container +podman stop qr-app + +# Start container +podman start qr-app + +# Remove container +podman rm qr-app + +# Remove image +podman rmi qr-code-generator +``` + +### Docker Commands + +Replace `podman` with `docker` in the commands above. + +## Troubleshooting + +### Issue: QR Code Won't Scan +- **Solution**: Increase error correction level to H +- Reduce logo size ratio +- Ensure sufficient contrast between QR code and background colors + +### Issue: Logo Appears Distorted +- **Solution**: Use square or nearly square logos +- Try a smaller logo size ratio +- Ensure logo has sufficient resolution + +### Issue: Container Won't Start +- **Solution**: Check if port 8501 is already in use +- Verify Podman/Docker is running +- Check container logs: `podman logs qr-app` + +### Issue: Dependencies Install Failure +- **Solution**: Update pip: `pip install --upgrade pip` +- Install system dependencies (for Pillow): `apt-get install libjpeg-dev zlib1g-dev` +- Use Python 3.11 or higher + +## Performance Considerations + +- **High-Resolution Exports**: May take a few seconds to generate +- **SVG Format**: Fastest generation, smallest file size, infinite scalability +- **Logo Processing**: Larger logos require more processing time + +## Security Notes + +- This application runs locally by default +- When deploying publicly, consider: + - Adding authentication + - Implementing rate limiting + - Validating URL inputs + - Setting up HTTPS + +## Contributing + +To contribute to this project: +1. Follow the existing code style and structure +2. Add appropriate documentation +3. Test thoroughly before submitting +4. Keep commits focused and descriptive + +## License + +This project is provided as-is for educational and commercial use. + +## Acknowledgments + +- Built with [Streamlit](https://streamlit.io/) +- QR code generation by [qrcode](https://github.com/lincolnloop/python-qrcode) and [segno](https://github.com/heuer/segno) +- Image processing by [Pillow](https://python-pillow.org/) + +## Support + +For issues, questions, or suggestions: +- Check the troubleshooting section +- Review the usage guide +- Examine the code documentation in `utils/qr_generator.py` + +--- + +**Version**: 1.0 +**Last Updated**: 2025-11 +**Python Version**: 3.11+ diff --git a/app.py b/app.py new file mode 100644 index 0000000..884a024 --- /dev/null +++ b/app.py @@ -0,0 +1,285 @@ +""" +QR Code Generator - Streamlit Application + +A web application for generating QR codes with optional logo overlay +and export in multiple formats and resolutions. +""" + +import streamlit as st +from PIL import Image +import io +from utils.qr_generator import QRCodeGenerator +from utils.wifi_qr import WiFiQRCode + + +# Page configuration +st.set_page_config( + page_title="QR Code Generator", + page_icon="📱", + layout="centered", + initial_sidebar_state="expanded" +) + + +def main(): + """Main application function.""" + st.title("QR Code Generator") + st.markdown("Generate professional QR codes with optional logo overlay") + + # Sidebar configuration + with st.sidebar: + st.header("Configuration") + + # Error correction level + error_correction = st.selectbox( + "Error Correction Level", + options=["L (7%)", "M (15%)", "Q (25%)", "H (30%)"], + index=3, + help="Higher levels allow more damage/obscurity. Use H (30%) for logos." + ) + error_level = error_correction[0] + + # QR code appearance + st.subheader("Appearance") + fill_color = st.color_picker("QR Code Color", "#000000") + back_color = st.color_picker("Background Color", "#FFFFFF") + + # Export settings + st.subheader("Export Settings") + export_format = st.selectbox( + "Format", + options=["PNG", "JPEG", "SVG"], + help="SVG is vector format (scalable), PNG/JPEG are raster formats" + ) + + if export_format in ["PNG", "JPEG"]: + # Define resolution options + resolution_labels = { + 500: "Low (500px)", + 1000: "Medium (1000px)", + 2000: "High (2000px)", + 4000: "Ultra (4000px)" + } + target_size = st.select_slider( + "Resolution", + options=[500, 1000, 2000, 4000], + value=2000, # Default to High + format_func=lambda x: resolution_labels[x] + ) + else: + svg_scale = st.slider( + "SVG Scale", + min_value=5, + max_value=20, + value=10, + help="Scale factor for SVG output" + ) + + # Main content area + st.divider() + + # Mode selection + qr_mode = st.radio( + "QR Code Type", + options=["URL", "WiFi"], + horizontal=True, + help="Choose what type of QR code to generate" + ) + + st.divider() + + # Content input based on mode + if qr_mode == "URL": + # URL input + url = st.text_input( + "Enter URL", + placeholder="https://example.com", + help="Enter the URL you want to encode in the QR code" + ) + qr_content = url + else: + # WiFi input fields + st.subheader("WiFi Network Details") + + col1, col2 = st.columns(2) + + with col1: + wifi_ssid = st.text_input( + "Network Name (SSID)", + placeholder="MyWiFiNetwork", + help="The name of your WiFi network" + ) + + wifi_security = st.selectbox( + "Security Type", + options=["WPA/WPA2", "WEP", "None (Open)"], + help="WiFi security/encryption type" + ) + + with col2: + wifi_password = st.text_input( + "Password", + type="password", + placeholder="Enter WiFi password", + help="Leave empty for open networks" + ) + + wifi_hidden = st.checkbox( + "Hidden Network", + value=False, + help="Check if this is a hidden network" + ) + + # Map security display to QR code format + security_map = { + "WPA/WPA2": "WPA", + "WEP": "WEP", + "None (Open)": "nopass" + } + + # Generate WiFi QR string + if wifi_ssid: + wifi_qr = WiFiQRCode( + ssid=wifi_ssid, + password=wifi_password if wifi_security != "None (Open)" else "", + security=security_map[wifi_security], + hidden=wifi_hidden + ) + qr_content = wifi_qr.to_string() + else: + qr_content = "" + + # Logo upload (optional) + st.subheader("Logo (Optional)") + logo_file = st.file_uploader( + "Upload logo image", + type=["png", "jpg", "jpeg"], + help="Upload a logo to place in the center of the QR code" + ) + + if logo_file: + logo_size_ratio = st.slider( + "Logo Size", + min_value=0.1, + max_value=0.4, + value=0.3, + step=0.05, + help="Logo size as ratio of QR code size" + ) + + # Generate button + if st.button("Generate QR Code", type="primary", use_container_width=True): + if not qr_content: + if qr_mode == "URL": + st.error("Please enter a URL") + else: + st.error("Please enter WiFi network name (SSID)") + return + + try: + with st.spinner("Generating QR code..."): + # Initialize generator + generator = QRCodeGenerator(qr_content, error_level) + + # Generate QR code + if export_format == "SVG": + # Generate SVG directly + svg_data = generator.generate_svg(scale=svg_scale) + + # Display preview (SVG as image) + st.success("QR Code generated successfully!") + st.image(svg_data, caption="QR Code Preview") + + # Download button + file_prefix = "wifi_qr" if qr_mode == "WiFi" else "url_qr" + st.download_button( + label=f"Download QR Code (SVG)", + data=svg_data, + file_name=f"{file_prefix}.svg", + mime="image/svg+xml", + use_container_width=True + ) + else: + # Generate raster image (PNG/JPEG) + qr_image = generator.generate_qr_image( + box_size=20, + border=4, + fill_color=fill_color, + back_color=back_color + ) + + # Add logo if provided + if logo_file: + logo_image = Image.open(logo_file) + qr_image = generator.add_logo( + qr_image, + logo_image, + logo_size_ratio + ) + + # Resize to target resolution + qr_image = generator.resize_image( + qr_image, + (target_size, target_size) + ) + + # Export image + image_data = generator.export_image( + qr_image, + format=export_format, + quality=95 + ) + + # Display preview + st.success("QR Code generated successfully!") + st.image(qr_image, caption="QR Code Preview") + + # Download button + mime_types = { + "PNG": "image/png", + "JPEG": "image/jpeg" + } + + file_prefix = "wifi_qr" if qr_mode == "WiFi" else "url_qr" + st.download_button( + label=f"Download QR Code ({export_format}, {target_size}px)", + data=image_data, + file_name=f"{file_prefix}.{export_format.lower()}", + mime=mime_types[export_format], + use_container_width=True + ) + + # Display QR code info + with st.expander("QR Code Information"): + st.write(f"**Type:** {qr_mode}") + if qr_mode == "URL": + st.write(f"**URL:** {qr_content}") + else: + st.write(f"**Network:** {wifi_ssid}") + st.write(f"**Security:** {wifi_security}") + st.write(f"**Hidden:** {'Yes' if wifi_hidden else 'No'}") + st.write(f"**Error Correction:** {error_correction}") + st.write(f"**Format:** {export_format}") + if export_format in ["PNG", "JPEG"]: + st.write(f"**Resolution:** {target_size}x{target_size}px") + st.write(f"**Logo:** {'Yes' if logo_file else 'No'}") + + except Exception as e: + st.error(f"Error generating QR code: {str(e)}") + st.exception(e) + + # Footer + st.divider() + st.markdown( + """ +
+

Built with Streamlit | QR Code Generator v1.0

+

Tip: Use high error correction (H) when adding logos

+
+ """, + unsafe_allow_html=True + ) + + +if __name__ == "__main__": + main() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..d50b082 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +streamlit==1.31.1 +qrcode[pil]==7.4.2 +Pillow==10.2.0 +segno==1.6.1 +cairosvg==2.7.1 diff --git a/run-podman.sh b/run-podman.sh new file mode 100755 index 0000000..4ebda6c --- /dev/null +++ b/run-podman.sh @@ -0,0 +1,31 @@ +#!/bin/bash +# Script to build and run QR Code Generator with Podman + +set -e + +IMAGE_NAME="qr-code-generator" +CONTAINER_NAME="qr-code-generator-app" +PORT=8501 + +echo "Building QR Code Generator container..." +podman build -t $IMAGE_NAME . + +echo "Stopping existing container if running..." +podman stop $CONTAINER_NAME 2>/dev/null || true +podman rm $CONTAINER_NAME 2>/dev/null || true + +echo "Starting QR Code Generator container..." +podman run -d \ + --name $CONTAINER_NAME \ + -p $PORT:8501 \ + --restart unless-stopped \ + $IMAGE_NAME + +echo "Container started successfully!" +echo "Access the application at: http://localhost:$PORT" +echo "" +echo "Useful commands:" +echo " podman logs $CONTAINER_NAME # View logs" +echo " podman stop $CONTAINER_NAME # Stop container" +echo " podman start $CONTAINER_NAME # Start container" +echo " podman rm $CONTAINER_NAME # Remove container" diff --git a/utils/__init__.py b/utils/__init__.py new file mode 100644 index 0000000..e455601 --- /dev/null +++ b/utils/__init__.py @@ -0,0 +1 @@ +"""Utility modules for QR code generation.""" diff --git a/utils/qr_generator.py b/utils/qr_generator.py new file mode 100644 index 0000000..d82ea5d --- /dev/null +++ b/utils/qr_generator.py @@ -0,0 +1,175 @@ +"""QR code generation utilities with logo overlay support.""" + +import io +from typing import Optional, Tuple +from PIL import Image, ImageDraw +import qrcode +import segno + + +class QRCodeGenerator: + """Generate QR codes with optional logo overlay and multiple export formats.""" + + def __init__(self, url: str, error_correction: str = "H"): + """ + Initialize QR code generator. + + Args: + url: The URL to encode in the QR code + error_correction: Error correction level (L, M, Q, H) + H (30%) recommended for logo overlay + """ + self.url = url + self.error_correction = self._get_error_correction(error_correction) + + def _get_error_correction(self, level: str) -> int: + """Convert error correction level string to qrcode constant.""" + levels = { + "L": qrcode.constants.ERROR_CORRECT_L, + "M": qrcode.constants.ERROR_CORRECT_M, + "Q": qrcode.constants.ERROR_CORRECT_Q, + "H": qrcode.constants.ERROR_CORRECT_H, + } + return levels.get(level, qrcode.constants.ERROR_CORRECT_H) + + def generate_qr_image( + self, + box_size: int = 10, + border: int = 4, + fill_color: str = "black", + back_color: str = "white" + ) -> Image.Image: + """ + Generate QR code as PIL Image. + + Args: + box_size: Size of each box in pixels + border: Border size in boxes + fill_color: QR code color + back_color: Background color + + Returns: + PIL Image object of the QR code + """ + qr = qrcode.QRCode( + version=1, + error_correction=self.error_correction, + box_size=box_size, + border=border, + ) + qr.add_data(self.url) + qr.make(fit=True) + + img = qr.make_image(fill_color=fill_color, back_color=back_color) + return img.convert("RGB") + + def add_logo( + self, + qr_image: Image.Image, + logo_image: Image.Image, + logo_size_ratio: float = 0.3 + ) -> Image.Image: + """ + Add logo to center of QR code. + + Args: + qr_image: QR code image + logo_image: Logo image to overlay + logo_size_ratio: Logo size as ratio of QR code size (default 0.3) + + Returns: + QR code image with logo overlay + """ + # Calculate logo size + qr_width, qr_height = qr_image.size + logo_max_size = int(min(qr_width, qr_height) * logo_size_ratio) + + # Resize logo while maintaining aspect ratio + logo_image = logo_image.convert("RGBA") + logo_image.thumbnail((logo_max_size, logo_max_size), Image.Resampling.LANCZOS) + + # Create white background for logo + logo_bg_size = int(logo_max_size * 1.1) + logo_bg = Image.new("RGB", (logo_bg_size, logo_bg_size), "white") + + # Calculate positions + logo_bg_pos = ( + (qr_width - logo_bg_size) // 2, + (qr_height - logo_bg_size) // 2 + ) + logo_pos = ( + (logo_bg_size - logo_image.size[0]) // 2, + (logo_bg_size - logo_image.size[1]) // 2 + ) + + # Paste logo background onto QR code + qr_image.paste(logo_bg, logo_bg_pos) + + # Paste logo with transparency + absolute_logo_pos = ( + logo_bg_pos[0] + logo_pos[0], + logo_bg_pos[1] + logo_pos[1] + ) + qr_image.paste(logo_image, absolute_logo_pos, logo_image) + + return qr_image + + def generate_svg(self, scale: int = 10) -> bytes: + """ + Generate QR code as SVG. + + Args: + scale: Scale factor for SVG + + Returns: + SVG data as bytes + """ + qr = segno.make(self.url, error="h") + buffer = io.BytesIO() + qr.save(buffer, kind="svg", scale=scale, border=4) + return buffer.getvalue() + + def resize_image( + self, + image: Image.Image, + target_size: Tuple[int, int] + ) -> Image.Image: + """ + Resize image to target size. + + Args: + image: PIL Image to resize + target_size: Target (width, height) in pixels + + Returns: + Resized PIL Image + """ + return image.resize(target_size, Image.Resampling.LANCZOS) + + def export_image( + self, + image: Image.Image, + format: str = "PNG", + quality: int = 95 + ) -> bytes: + """ + Export image to specified format. + + Args: + image: PIL Image to export + format: Output format (PNG, JPEG, etc.) + quality: Quality for JPEG (1-100) + + Returns: + Image data as bytes + """ + buffer = io.BytesIO() + + if format.upper() == "JPEG": + # JPEG doesn't support transparency, convert to RGB + image = image.convert("RGB") + image.save(buffer, format=format, quality=quality, optimize=True) + else: + image.save(buffer, format=format, optimize=True) + + return buffer.getvalue() diff --git a/utils/wifi_qr.py b/utils/wifi_qr.py new file mode 100644 index 0000000..eb6b737 --- /dev/null +++ b/utils/wifi_qr.py @@ -0,0 +1,99 @@ +"""WiFi QR code generation utilities.""" + +from typing import Optional + + +class WiFiQRCode: + """Generate WiFi QR code strings in the standard format.""" + + def __init__( + self, + ssid: str, + password: str, + security: str = "WPA", + hidden: bool = False + ): + """ + Initialize WiFi QR code generator. + + Args: + ssid: WiFi network name (SSID) + password: WiFi password + security: Security type (WPA, WEP, or nopass) + hidden: Whether the network is hidden + """ + self.ssid = ssid + self.password = password + self.security = security.upper() + self.hidden = hidden + + @staticmethod + def _escape_special_chars(text: str) -> str: + """ + Escape special characters for WiFi QR code format. + + Special characters that need escaping: \ ; , " : + + Args: + text: Text to escape + + Returns: + Escaped text + """ + # Characters that need to be escaped with backslash + special_chars = ['\\', ';', ',', '"', ':'] + + result = text + for char in special_chars: + result = result.replace(char, f'\\{char}') + + return result + + def generate_wifi_string(self) -> str: + """ + Generate WiFi QR code string in standard format. + + Format: WIFI:T:;S:;P:;H:;; + + Returns: + Formatted WiFi QR code string + """ + # Escape special characters in SSID and password + escaped_ssid = self._escape_special_chars(self.ssid) + escaped_password = self._escape_special_chars(self.password) + + # Build WiFi string + wifi_string = f"WIFI:T:{self.security};" + wifi_string += f"S:{escaped_ssid};" + + # Only add password if security is not 'nopass' + if self.security.upper() != "NOPASS": + wifi_string += f"P:{escaped_password};" + + # Add hidden flag + wifi_string += f"H:{'true' if self.hidden else 'false'};;" + + return wifi_string + + @staticmethod + def validate_security_type(security: str) -> bool: + """ + Validate security type. + + Args: + security: Security type to validate + + Returns: + True if valid, False otherwise + """ + valid_types = ["WPA", "WEP", "NOPASS"] + return security.upper() in valid_types + + def to_string(self) -> str: + """ + Convert WiFi credentials to QR code string. + + Returns: + WiFi QR code string + """ + return self.generate_wifi_string()