habulaj commited on
Commit
1fb5c6b
·
verified ·
1 Parent(s): 06d76f9

Update routers/news.py

Browse files
Files changed (1) hide show
  1. routers/news.py +139 -18
routers/news.py CHANGED
@@ -229,25 +229,133 @@ def render_responsive_text(draw: ImageDraw.Draw, text: str, x: int, y: int, max_
229
  current_y -= int(line_height * 1.20)
230
  draw.text((x, current_y), line, fill=fill_color, font=final_font)
231
 
232
- def create_canvas(image_url: Optional[str], text: Optional[str] = None, text_position: str = "bottom", text_color: str = "white") -> BytesIO:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
233
  # Dimensões fixas do Instagram
234
  width, height = 1080, 1350
235
 
236
  # Configurações da logo
237
  logo_width, logo_height = 121, 23
238
 
239
- canvas = Image.new("RGBA", (width, height), color=(255, 255, 255, 255))
 
240
 
241
- # Adicionar imagem de fundo se fornecida
242
- if image_url:
 
 
 
243
  img = download_image_from_url(image_url)
244
  filled_img = resize_and_crop_to_fill(img, width, height)
245
  canvas.paste(filled_img, (0, 0))
246
-
247
- # Adicionar gradiente (apenas se não for texto preto)
248
- if text_color.lower() != "black":
249
- gradient_overlay = create_gradient_overlay(width, height, text_position)
250
- canvas = Image.alpha_composite(canvas, gradient_overlay)
 
 
 
251
 
252
  # Adicionar logo na nova posição (X: 880, Y: 1260)
253
  try:
@@ -272,14 +380,26 @@ def create_canvas(image_url: Optional[str], text: Optional[str] = None, text_pos
272
  if text and text.strip():
273
  draw = ImageDraw.Draw(canvas)
274
 
275
- # Determinar posição Y baseada no text_position
276
- if text_position.lower() == "top":
277
- text_y = 60 # Posição para texto no topo com espaçamento de 60px
 
 
 
 
 
 
278
  else:
279
- text_y = 1180 # Posição para texto embaixo (padrão)
280
-
281
- # Configurações do texto: X: 78, largura: 924px, alinhado à base e à esquerda
282
- render_responsive_text(draw, text, x=78, y=text_y, max_width=924, max_lines=3, text_color=text_color, text_position=text_position)
 
 
 
 
 
 
283
 
284
  buffer = BytesIO()
285
  canvas.convert("RGB").save(buffer, format="PNG")
@@ -291,10 +411,11 @@ def get_news_image(
291
  image_url: Optional[str] = Query(None, description="URL da imagem de fundo"),
292
  text: Optional[str] = Query(None, description="Texto a ser exibido na imagem"),
293
  text_position: str = Query("bottom", description="Posição do texto: 'bottom' ou 'top'"),
294
- text_color: str = Query("white", description="Cor do texto: 'white' ou 'black'")
 
295
  ):
296
  try:
297
- buffer = create_canvas(image_url, text, text_position, text_color)
298
  return StreamingResponse(buffer, media_type="image/png")
299
  except Exception as e:
300
  raise HTTPException(status_code=500, detail=f"Erro ao gerar imagem: {str(e)}")
 
229
  current_y -= int(line_height * 1.20)
230
  draw.text((x, current_y), line, fill=fill_color, font=final_font)
231
 
232
+ def render_description_text(draw: ImageDraw.Draw, text: str, x: int, y: int, max_width: int, max_lines: int = 5) -> None:
233
+ """
234
+ Renderiza texto de descrição com fonte Montserrat Regular 32px.
235
+
236
+ Args:
237
+ draw: Objeto ImageDraw para desenhar
238
+ text: Texto da descrição
239
+ x: Posição X (esquerda)
240
+ y: Posição Y (topo - texto alinhado de cima para baixo)
241
+ max_width: Largura máxima do texto
242
+ max_lines: Número máximo de linhas (padrão: 5)
243
+ """
244
+ if not text.strip():
245
+ return
246
+
247
+ # Carregar fonte Montserrat Regular
248
+ try:
249
+ font_path = "fonts/Montserrat-Regular.ttf"
250
+ base_font = ImageFont.truetype(font_path, 32)
251
+ except (OSError, IOError):
252
+ # Fallback para fonte padrão se a fonte personalizada não for encontrada
253
+ base_font = ImageFont.load_default()
254
+
255
+ # Dividir texto em palavras
256
+ words = text.split()
257
+ if not words:
258
+ return
259
+
260
+ # Função para quebrar texto em linhas
261
+ def wrap_text(text, font_size, max_width):
262
+ try:
263
+ test_font = ImageFont.truetype(font_path, font_size)
264
+ except (OSError, IOError):
265
+ test_font = ImageFont.load_default()
266
+
267
+ lines = []
268
+ current_line = []
269
+
270
+ for word in words:
271
+ test_line = " ".join(current_line + [word])
272
+ bbox = draw.textbbox((0, 0), test_line, font=test_font)
273
+ line_width = bbox[2] - bbox[0]
274
+
275
+ if line_width <= max_width:
276
+ current_line.append(word)
277
+ else:
278
+ if current_line:
279
+ lines.append(" ".join(current_line))
280
+ current_line = [word]
281
+ else:
282
+ # Palavra muito longa, adiciona mesmo assim
283
+ lines.append(word)
284
+
285
+ if current_line:
286
+ lines.append(" ".join(current_line))
287
+
288
+ return lines
289
+
290
+ # Encontrar o tamanho de fonte ideal
291
+ font_size = 32 # Tamanho inicial
292
+ min_font_size = 16 # Tamanho mínimo
293
+
294
+ # Tentar diferentes tamanhos de fonte até encontrar um que caiba em max_lines
295
+ while font_size >= min_font_size:
296
+ lines = wrap_text(text, font_size, max_width)
297
+
298
+ # Se o texto cabe em max_lines ou menos, usar este tamanho
299
+ if len(lines) <= max_lines:
300
+ break
301
+
302
+ # Reduzir o tamanho da fonte
303
+ font_size -= 2
304
+
305
+ # Garantir que não seja menor que o tamanho mínimo
306
+ font_size = max(font_size, min_font_size)
307
+
308
+ # Carregar fonte final
309
+ try:
310
+ final_font = ImageFont.truetype(font_path, font_size)
311
+ except (OSError, IOError):
312
+ final_font = ImageFont.load_default()
313
+
314
+ # Quebrar texto final
315
+ lines = wrap_text(text, font_size, max_width)
316
+
317
+ # Se ainda não couber em max_lines, forçar quebra nas primeiras max_lines
318
+ if len(lines) > max_lines:
319
+ # Combinar as linhas restantes na última linha permitida
320
+ combined_text = " ".join(lines[:max_lines-1] + [" ".join(lines[max_lines-1:])])
321
+ lines = wrap_text(combined_text, font_size, max_width)
322
+ lines = lines[:max_lines] # Garantir que não exceda max_lines
323
+
324
+ # Desenhar texto de cima para baixo (alinhamento ao topo)
325
+ current_y = y
326
+ for line in lines:
327
+ bbox = draw.textbbox((0, 0), line, font=final_font)
328
+ line_height = bbox[3] - bbox[1]
329
+ draw.text((x, current_y), line, fill=(255, 255, 255, 255), font=final_font)
330
+ # Aplicar line height de 120%
331
+ current_y += int(line_height * 1.20)
332
+
333
+ def create_canvas(image_url: Optional[str], text: Optional[str] = None, text_position: str = "bottom", text_color: str = "white", description: Optional[str] = None) -> BytesIO:
334
  # Dimensões fixas do Instagram
335
  width, height = 1080, 1350
336
 
337
  # Configurações da logo
338
  logo_width, logo_height = 121, 23
339
 
340
+ # Determinar se é modo com ou sem imagem
341
+ has_image = image_url is not None and image_url.strip() != ""
342
 
343
+ if has_image:
344
+ # Modo com imagem
345
+ canvas = Image.new("RGBA", (width, height), color=(255, 255, 255, 255))
346
+
347
+ # Adicionar imagem de fundo
348
  img = download_image_from_url(image_url)
349
  filled_img = resize_and_crop_to_fill(img, width, height)
350
  canvas.paste(filled_img, (0, 0))
351
+
352
+ # Adicionar gradiente (apenas se não for texto preto)
353
+ if text_color.lower() != "black":
354
+ gradient_overlay = create_gradient_overlay(width, height, text_position)
355
+ canvas = Image.alpha_composite(canvas, gradient_overlay)
356
+ else:
357
+ # Modo sem imagem - fundo escuro
358
+ canvas = Image.new("RGBA", (width, height), color=(18, 18, 18, 255)) # #121212
359
 
360
  # Adicionar logo na nova posição (X: 880, Y: 1260)
361
  try:
 
380
  if text and text.strip():
381
  draw = ImageDraw.Draw(canvas)
382
 
383
+ if has_image:
384
+ # Modo com imagem - usar text_position
385
+ if text_position.lower() == "top":
386
+ text_y = 60 # Posição para texto no topo com espaçamento de 60px
387
+ else:
388
+ text_y = 1180 # Posição para texto embaixo (padrão)
389
+
390
+ # Configurações do texto: X: 78, largura: 924px, alinhado à base e à esquerda
391
+ render_responsive_text(draw, text, x=78, y=text_y, max_width=924, max_lines=3, text_color=text_color, text_position=text_position)
392
  else:
393
+ # Modo sem imagem - texto sempre no topo, máximo 4 linhas, sempre branco
394
+ text_y = 60 # Posição fixa no topo
395
+ render_responsive_text(draw, text, x=78, y=text_y, max_width=924, max_lines=4, text_color="white", text_position="top")
396
+
397
+ # Adicionar descrição se fornecida e estiver no modo sem imagem
398
+ if not has_image and description and description.strip():
399
+ draw = ImageDraw.Draw(canvas)
400
+ # Descrição na parte inferior
401
+ description_y = 1000 # Posição na parte inferior
402
+ render_description_text(draw, description, x=78, y=description_y, max_width=924, max_lines=5)
403
 
404
  buffer = BytesIO()
405
  canvas.convert("RGB").save(buffer, format="PNG")
 
411
  image_url: Optional[str] = Query(None, description="URL da imagem de fundo"),
412
  text: Optional[str] = Query(None, description="Texto a ser exibido na imagem"),
413
  text_position: str = Query("bottom", description="Posição do texto: 'bottom' ou 'top'"),
414
+ text_color: str = Query("white", description="Cor do texto: 'white' ou 'black'"),
415
+ description: Optional[str] = Query(None, description="Descrição (só funciona quando image_url não for fornecido)")
416
  ):
417
  try:
418
+ buffer = create_canvas(image_url, text, text_position, text_color, description)
419
  return StreamingResponse(buffer, media_type="image/png")
420
  except Exception as e:
421
  raise HTTPException(status_code=500, detail=f"Erro ao gerar imagem: {str(e)}")