CoolFace
Apppublic

acmc/PDFuzz

sourceHugging Faceupdated 1y agoView on Hugging Face
0likes
gradio_demo.py215 linesDownload Raw Back to root
1#!/usr/bin/env python32"""Simple Gradio demo for the PDF attacker tools3 4Allows entering text, choosing attack type, and downloading the generated PDF.5"""6import os7import time8from typing import Tuple9 10import PyPDF211import gradio as gr12 13from pdf_attacker import PDFAttacker14 15 16def _resolve_font_path(choice: str, uploaded_file) -> str:17    """Return a font path given a dropdown choice or uploaded file.18 19    If choice is 'auto' return None so PDFAttacker will pick a reasonable default.20    """21    if choice == 'auto' or not choice:22        return None23 24    # known presets mapped to candidate system paths (try first existing)25    presets = {26        'DejaVu Serif': [27            '/usr/share/fonts/truetype/dejavu/DejaVuSerif.ttf',28        ],29        'Liberation Serif': [30            '/usr/share/fonts/truetype/liberation/LiberationSerif-Regular.ttf',31        ],32        'FreeSerif': [33            '/usr/share/fonts/truetype/freefont/FreeSerif.ttf',34        ],35        'DejaVu Sans': [36            '/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf',37        ],38        'Arial': [39            '/usr/share/fonts/truetype/msttcorefonts/Arial.ttf',40            '/usr/share/fonts/truetype/msttcorefonts/arial.ttf',41            '/usr/share/fonts/truetype/arial/arial.ttf',42            '/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf',43        ],44        'Helvetica': [45            '/usr/share/fonts/truetype/urw-base35/Helvetica.ttf',46            '/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf',47            '/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf',48        ],49        'Times New Roman': [50            '/usr/share/fonts/truetype/msttcorefonts/Times_New_Roman.ttf',51            '/usr/share/fonts/truetype/msttcorefonts/Times_New_Roman.ttf',52            '/usr/share/fonts/truetype/liberation/LiberationSerif-Regular.ttf',53        ],54        'Roboto': [55            '/usr/share/fonts/truetype/roboto/Roboto-Regular.ttf',56            '/usr/share/fonts/truetype/roboto/Roboto-Regular.ttf',57        ],58        'Courier': [59            '/usr/share/fonts/truetype/liberation/LiberationMono-Regular.ttf',60            '/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf',61        ],62        'Times': [63            '/usr/share/fonts/truetype/liberation/LiberationSerif-Regular.ttf',64        ],65    }66 67    if choice in presets:68        for p in presets[choice]:69            if os.path.exists(p):70                return p71        return None72 73    # custom uploaded file: gradio returns a local path-like string or dict74    if choice == 'Custom' and uploaded_file:75        # uploaded_file may be a dict-like object or a str path76        if isinstance(uploaded_file, dict) and 'name' in uploaded_file:77            return uploaded_file['name']78        return uploaded_file79 80    return None81 82# Theme customization per request83theme = gr.themes.Soft(84    primary_hue="fuchsia",85    secondary_hue="cyan",86    neutral_hue="gray",87    radius_size="none",88    font=[89        gr.themes.GoogleFont("IBM Plex Sans"),90        "ui-sans-serif",91        "system-ui",92        "sans-serif",93    ],94    font_mono=[95        gr.themes.GoogleFont("IBM Plex Mono"),96        "ui-monospace",97        "Consolas",98        "monospace",99    ],100)101 102 103def _ensure_tmp_dir() -> str:104    """Ensure tmp dir exists and return its path"""105    path = os.path.join(os.getcwd(), "tmp")106    os.makedirs(path, exist_ok=True)107    return path108 109 110def _extract_text_from_pdf(pdf_path: str) -> str:111    """Extract text from a PDF file for preview"""112    try:113        with open(pdf_path, 'rb') as f:114            reader = PyPDF2.PdfReader(f)115            text = ""116            for page in reader.pages:117                page_text = page.extract_text()118                if page_text:119                    text += page_text120        return text.strip()121    except Exception as e:122        return f"Error extracting text: {e}"123 124 125def generate_pdf(126    text: str,127    mode: str,128    attack_factor: float = 0.7,129    target_text: str = "",130    font_choice: str = 'auto',131    uploaded_font=None,132    wrap_on_words: bool = True,133) -> Tuple[str, str, str]:134    """Generate selected PDF and return (pdf_path, extracted_text)135 136    Inputs: text, mode: 'normal'|'attacked'|'targeted', attack_factor, target_text137    Outputs: path to generated PDF, extracted text preview138    """139    tmp_dir = _ensure_tmp_dir()140    timestamp = int(time.time() * 1000)141    filename = f"{mode}_{timestamp}.pdf"142    output_path = os.path.join(tmp_dir, filename)143 144    # Clean input text145    clean_text = " ".join(text.split())146 147    # resolve font path and create an attacker instance for this request148    font_path = _resolve_font_path(choice=font_choice, uploaded_file=uploaded_font)149    attacker = PDFAttacker(font_path=font_path)150    # apply wrap mode151    attacker.wrap_on_words = wrap_on_words152 153    # Build a contextual status string for the UI154    resolved_font = font_path or "(auto/default)"155    status_lines = [f"Font resolved to: {resolved_font}", f"Wrap on words: {wrap_on_words}"]156 157    try:158        if mode == 'normal':159            attacker.create_normal_pdf(text=clean_text, output_path=output_path)160        elif mode == 'attacked':161            attacker.create_attacked_pdf(text=clean_text, output_path=output_path, attack_factor=attack_factor)162        elif mode == 'targeted':163            # Targeted may raise ValueError if not feasible164            attacker.create_targeted_pdf(text=clean_text, target_text=target_text, output_path=output_path)165        else:166            return "", f"Unknown mode: {mode}"167 168    except Exception as e:169        # Surface errors to the UI170        return "", f"Error extracting text: {e}", f"Error: {e}"171 172    # Extract text to show how the copied/extracted text looks173    extracted = _extract_text_from_pdf(output_path)174 175    return output_path, extracted, "\n".join(status_lines)176 177 178def build_demo():179    """Construct and return the Gradio Blocks demo"""180    with gr.Blocks(theme=theme) as demo:181        gr.Markdown("# PDF Humanizer: Attack demo\nGenerate PDFs that look normal but extract differently when copied")182 183        with gr.Row():184            txt = gr.Textbox(lines=8, label="Input text", value="Enter or paste text here...")185            with gr.Column():186                mode = gr.Radio(choices=['normal', 'attacked', 'targeted'], value='attacked', label='Mode')187                attack_factor = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, value=0.7, label='Attack factor (attacked mode)')188                target_text = gr.Textbox(lines=2, label='Target text (targeted mode)')189                generate = gr.Button('Generate PDF')190                191                # Font selection: presets + custom upload192                font_choice = gr.Dropdown(choices=['auto', 'DejaVu Serif', 'Liberation Serif', 'FreeSerif', 'Arial', 'Helvetica', 'Times New Roman', 'Roboto', 'Courier', 'Custom'], value='auto', label='Font')193                upload_font = gr.File(label='Upload TTF/OTF (optional)', file_count='single')194                wrap_on_words = gr.Checkbox(label='Wrap on words', value=True)195 196        download_file = gr.File(label='Download generated PDF')197        extracted_preview = gr.Textbox(lines=8, label='Extracted text preview')198        status_box = gr.Textbox(lines=4, label='Status')199 200        def _on_generate(text, mode, attack_factor, target_text, font_choice, upload_font, wrap_on_words):201            path, extracted, status = generate_pdf(text=text, mode=mode, attack_factor=attack_factor, target_text=target_text, font_choice=font_choice, uploaded_font=upload_font, wrap_on_words=wrap_on_words)202            if not path:203                # Return empty file and error message in preview204                return None, extracted, status205            return path, extracted, status206 207        generate.click(fn=_on_generate, inputs=[txt, mode, attack_factor, target_text, font_choice, upload_font, wrap_on_words], outputs=[download_file, extracted_preview, status_box])208 209    return demo210 211 212if __name__ == '__main__':213    app = build_demo()214    app.launch(server_name='0.0.0.0', server_port=7860)215