import base64 import hashlib import json import os import random import shutil import string from io import BytesIO from urllib.parse import urlparse
import imgkit import requests from PIL import Image, ImageDraw, ImageFont from django.http import HttpResponse from django.views.decorators.csrf import csrf_exempt from fontTools.ttLib import TTFont
root_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
def init(): config_path = os.path.join(root_dir, "fonts.conf") if not os.path.exists(config_path): fonts_conf = f""" <?xml version="1.0"?> <!DOCTYPE fontconfig SYSTEM "fonts.dtd"> <fontconfig> <dir>{root_dir}/fonts</dir> <cachedir>{root_dir}/font_cache</cachedir> </fontconfig> """ with open(config_path, 'w') as f: f.write(fonts_conf.strip())
os.environ['FONTCONFIG_FILE'] = config_path
@csrf_exempt def getText(request): params = json.loads(request.body) init()
options = { 'format': 'png', 'transparent': "", 'quiet': "", }
img_name = ''.join(random.sample(string.ascii_letters + string.digits, 16)) image_path = '/tmp/' + img_name + '.png' html = build_html(params)
path_wkthmltoimage = '/usr/local/bin/wkhtmltoimage' config = imgkit.config(wkhtmltoimage=path_wkthmltoimage)
imgkit.from_string(html, image_path, options=options, config=config)
crop_image(image_path)
if 'strikethrough' in params.get('fontFormat', []): drawLine(image_path, float(params.get('fontSize')))
with open(image_path, 'rb') as f: file_content = f.read()
os.remove(image_path)
data = { 'base64_data': base64.b64encode(file_content).decode('utf-8'), 'size': len(file_content) }
img = Image.open(BytesIO(file_content)) data['width'], data['height'] = img.size md5_hash = hashlib.md5() md5_hash.update(file_content) data['etag'] = md5_hash.hexdigest()
return HttpResponse(json.dumps(data))
def get_text_size(text, font_path, font_size): temp_image = Image.new('RGB', (1, 1), color='white') draw = ImageDraw.Draw(temp_image)
font = ImageFont.truetype(font_path, font_size)
text_bbox = draw.textbbox((0, 0), text, font=font)
return text_bbox[2] - text_bbox[0] + 10
def build_html(params): style = "" divStyle = ""
fontSize = params.get('fontSize', '') fontColor = params.get('fontColor', '') fontOpcity = params.get('fontOpcity', '') text = params.get('text', '') text = text.replace("\n", "<br/>") fontName = getFontName(params.get('fontFamilyUrl')) fontFormat = params.get('fontFormat', [])
if fontName: style += "font-family: '" + fontName + "';"
if fontSize: style += "font-size: " + str(fontSize) + "px;"
if fontColor: if str(fontOpcity) != "1": rgb = Hex2RGB(fontColor) style += "color: rgba(" + rgb + "," + fontOpcity + ");" else: style += "color: " + fontColor + ";"
if fontAlign := params.get('fontAlign', ''): divStyle += "text-align: " + fontAlign + ";" if fontSpace := params.get('fontSpace', ''): style += "letter-spacing: " + fontSpace + "px;" if fontLineHeight := params.get('fontLineSpace', ''): style += "line-height: " + fontLineHeight + ";" if fontOutlineWidth := params.get('fontOutlineWidth', ''): fontOutlineColor = params.get('fontOutlineColor', '#000') style += '-webkit-text-stroke: ' + fontOutlineWidth + 'px ' + fontOutlineColor + ';' fontProjectionScopeX = params.get('fontProjectionScopeX', '0') fontProjectionScopeY = params.get('fontProjectionScopeY', '0') if fontProjectionScopeX or fontProjectionScopeY: fontProjectionScopeDim = params.get('fontProjectionScopeDim', '') fontProjectionColor = params.get('fontProjectionColor', '#000') style += 'text-shadow: ' + fontProjectionScopeX + 'px ' + fontProjectionScopeY + 'px ' + fontProjectionScopeDim + 'px ' + fontProjectionColor + ';' if 'bold' in fontFormat: style += "font-weight: bold;" if 'italic' in fontFormat: style += "font-style: italic;"
return f""" <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div style="{divStyle}"> <span style="{style}">{text}</span> </div> </body> </html> """
def getFontName(fontFamilyUrl): url_path = urlparse(fontFamilyUrl).path file_name = os.path.basename(url_path) fonts_dir = os.path.join(root_dir, "fonts") full_path = os.path.join(fonts_dir, file_name) if not os.path.exists(fonts_dir): os.makedirs(fonts_dir) if not os.path.exists(full_path): response = requests.get(fontFamilyUrl) with open(full_path, 'wb') as f: f.write(response.content) modifyFontName(full_path) if os.path.exists(os.path.join(root_dir, "font_cache")): shutil.rmtree(os.path.join(root_dir, "font_cache")) pass
return file_name
def crop_image(image_path): img = Image.open(image_path)
bbox = img.getbbox()
if bbox: img_cropped = img.crop(bbox)
img_cropped.save(image_path) else: print("图片是空的或全透明的,无法裁剪。")
def modifyFontName(font_path): file_name = os.path.basename(font_path) font = TTFont(font_path)
name_table = font['name'] for record in name_table.names: if record.nameID == 1 or record.nameID == 16 or record.nameID == 4: record.string = file_name.encode('utf-16be')
font.save(font_path)
print(f"字体名称已修改为:{file_name}")
def drawLine(url, fontSize): img = Image.open(url) draw = ImageDraw.Draw(img) width, height = img.size line_width = int(fontSize / 12) if int(fontSize / 12) > 1 else 1
draw.line((2, height / 2, width - 2, height / 2), fill=(0, 0, 0), width=line_width) img.save(url)
def Hex2RGB(hex): r = int(hex[1:3], 16) g = int(hex[3:5], 16) b = int(hex[5:7], 16) rgb = str(r) + ',' + str(g) + ',' + str(b) return rgb
|