import fitz import os def hex_to_rgb(hex_color): hex_color = hex_color.lstrip('#') return tuple(int(hex_color[i:i+2], 16)/255 for i in (0, 2, 4)) def generate_certificate_pdf(name, template_bytes, output_path, name_color_hex="#000000", font_size=60, x=None, y=None, fontname="helv", fontfile=None): """ Generates a certificate PDF by overlaying text. If x and y are provided, uses those coordinates. Otherwise, searches for placeholder. """ doc = fitz.open(stream=template_bytes, filetype="pdf") name_color = hex_to_rgb(name_color_hex) for page in doc: target_x, target_y = x, y # If no manual coordinates, try to find placeholder if target_x is None or target_y is None: found = False for inst in page.search_for(""): rect = fitz.Rect(inst) # Calculate center of the placeholder name_width = fitz.get_text_length(name, fontsize=font_size, fontname=fontname, fontfile=fontfile) target_x = rect.x0 + (rect.width - name_width) / 2 # For Y: use baseline position (rect.y1 is bottom of box) target_y = rect.y0 + rect.height / 2 + font_size / 3 found = True break if not found: # Fallback default if nothing found and no coordinates page_width = page.rect.width page_height = page.rect.height rect_width = int(page_width * 0.6) x0_default = int((page_width - rect_width) / 2) name_width = fitz.get_text_length(name, fontsize=font_size, fontname=fontname, fontfile=fontfile) target_x = x0_default + (rect_width - name_width) / 2 # Place at 60% down the page target_y = page_height * 0.6 # Insert text (ensure coordinates are within page bounds) if target_x is not None and target_y is not None: # Calculate text dimensions for centering if fontfile and os.path.exists(fontfile): font = fitz.Font(fontfile=fontfile) else: font = fitz.Font(fontname) name_width = font.text_length(name, fontsize=font_size) # Adjust x to center the text insert_x = target_x - (name_width / 2) # Adjust y from center to baseline (approximate) # Center is target_y. Baseline is typically target_y + font_size/3 insert_y = target_y + (font_size / 3) # Clamp coordinates to page bounds page_rect = page.rect insert_x = max(10, min(insert_x, page_rect.width - name_width - 10)) insert_y = max(font_size, min(insert_y, page_rect.height - 10)) page.insert_text( (insert_x, insert_y), name, fontsize=font_size, color=name_color, fontname=fontname, fontfile=fontfile ) doc.save(output_path) doc.close() return output_path def get_pdf_preview_image(template_bytes, name="Sample Name", name_color_hex="#000000", font_size=60, x=None, y=None, fontname="helv", fontfile=None): """ Generates a preview image (PNG) of the first page of the certificate. Returns bytes of the PNG image. """ doc = fitz.open(stream=template_bytes, filetype="pdf") name_color = hex_to_rgb(name_color_hex) page = doc[0] # Preview only first page target_x, target_y = x, y # Logic similar to generation, but just for one page and return image if target_x is None or target_y is None: found = False for inst in page.search_for(""): rect = fitz.Rect(inst) name_width = fitz.get_text_length(name, fontsize=font_size, fontname=fontname, fontfile=fontfile) target_x = rect.x0 + (rect.width - name_width) / 2 target_y = rect.y0 + rect.height / 2 + font_size / 3 found = True break if not found: page_width = page.rect.width page_height = page.rect.height rect_width = int(page_width * 0.6) x0_default = int((page_width - rect_width) / 2) name_width = fitz.get_text_length(name, fontsize=font_size, fontname=fontname, fontfile=fontfile) target_x = x0_default + (rect_width - name_width) / 2 import fitz import os def hex_to_rgb(hex_color): hex_color = hex_color.lstrip('#') return tuple(int(hex_color[i:i+2], 16)/255 for i in (0, 2, 4)) def generate_certificate_pdf(name, template_bytes, output_path, name_color_hex="#000000", font_size=60, x=None, y=None, fontname="helv", fontfile=None): """ Generates a certificate PDF by overlaying text. If x and y are provided, uses those coordinates. Otherwise, searches for placeholder. """ doc = fitz.open(stream=template_bytes, filetype="pdf") name_color = hex_to_rgb(name_color_hex) for page in doc: target_x, target_y = x, y # If no manual coordinates, try to find placeholder if target_x is None or target_y is None: found = False for inst in page.search_for(""): rect = fitz.Rect(inst) # Calculate center of the placeholder if fontfile and os.path.exists(fontfile): font = fitz.Font(fontfile=fontfile) else: font = fitz.Font(fontname) name_width = font.text_length(name, fontsize=font_size) target_x = rect.x0 + (rect.width - name_width) / 2 # For Y: use baseline position (rect.y1 is bottom of box) target_y = rect.y0 + rect.height / 2 + font_size / 3 found = True break if not found: # Fallback default if nothing found and no coordinates page_width = page.rect.width page_height = page.rect.height rect_width = int(page_width * 0.6) x0_default = int((page_width - rect_width) / 2) if fontfile and os.path.exists(fontfile): font = fitz.Font(fontfile=fontfile) else: font = fitz.Font(fontname) name_width = font.text_length(name, fontsize=font_size) target_x = x0_default + (rect_width - name_width) / 2 # Place at 60% down the page target_y = page_height * 0.6 # Insert text (ensure coordinates are within page bounds) if target_x is not None and target_y is not None: # Calculate text dimensions for centering if fontfile and os.path.exists(fontfile): font = fitz.Font(fontfile=fontfile) else: font = fitz.Font(fontname) name_width = font.text_length(name, fontsize=font_size) # Adjust x to center the text insert_x = target_x - (name_width / 2) # Adjust y from center to baseline (approximate) # Center is target_y. Baseline is typically target_y + font_size/3 insert_y = target_y + (font_size / 3) # Clamp coordinates to page bounds page_rect = page.rect insert_x = max(10, min(insert_x, page_rect.width - name_width - 10)) insert_y = max(font_size, min(insert_y, page_rect.height - 10)) page.insert_text( (insert_x, insert_y), name, fontsize=font_size, color=name_color, fontname=fontname, fontfile=fontfile ) doc.save(output_path) doc.close() return output_path def get_pdf_preview_image(template_bytes, name="Sample Name", name_color_hex="#000000", font_size=60, x=None, y=None, fontname="helv", fontfile=None): """ Generates a preview image (PNG) of the first page of the certificate. Returns bytes of the PNG image. """ doc = fitz.open(stream=template_bytes, filetype="pdf") name_color = hex_to_rgb(name_color_hex) page = doc[0] # Preview only first page target_x, target_y = x, y # Logic similar to generation, but just for one page and return image if target_x is None or target_y is None: found = False for inst in page.search_for(""): rect = fitz.Rect(inst) if fontfile and os.path.exists(fontfile): font = fitz.Font(fontfile=fontfile) else: font = fitz.Font(fontname) name_width = font.text_length(name, fontsize=font_size) target_x = rect.x0 + (rect.width - name_width) / 2 target_y = rect.y0 + rect.height / 2 + font_size / 3 found = True break if not found: page_width = page.rect.width page_height = page.rect.height rect_width = int(page_width * 0.6) x0_default = int((page_width - rect_width) / 2) if fontfile and os.path.exists(fontfile): font = fitz.Font(fontfile=fontfile) else: font = fitz.Font(fontname) name_width = font.text_length(name, fontsize=font_size) target_x = x0_default + (rect_width - name_width) / 2 target_y = page_height * 0.6 if target_x is not None and target_y is not None: # Calculate text dimensions for centering if fontfile and os.path.exists(fontfile): font = fitz.Font(fontfile=fontfile) else: font = fitz.Font(fontname) name_width = font.text_length(name, fontsize=font_size) # Adjust x to center the text insert_x = target_x - (name_width / 2) # Adjust y from center to baseline (approximate) # Center is target_y. Baseline is typically target_y + font_size/3 insert_y = target_y + (font_size / 3) # Clamp coordinates to page bounds page_rect = page.rect insert_x = max(10, min(insert_x, page_rect.width - name_width - 10)) insert_y = max(font_size, min(insert_y, page_rect.height - 10)) page.insert_text( (insert_x, insert_y), name, fontsize=font_size, color=name_color, fontname=fontname, fontfile=fontfile ) pix = page.get_pixmap() img_bytes = pix.tobytes("png") doc.close() return img_bytes