Refactor per-user channel registration and improve PDF/PPTX workflows
- Generalize per-user channel registration (telegram/discord/whatsapp) via vault instead of config.json userMap - Add API endpoints for self-service channel binding - Migrate Telegram channel to use vault-based username resolution - Improve PDF figure extraction filters (OpenCV) - Fix PPTX image embedding and font sizing - Reorder search providers: searxng → ddg priority Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+35
-13
@@ -38,6 +38,7 @@ import sys
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import math
|
||||
|
||||
# Force UTF-8 output on Windows
|
||||
if sys.platform == 'win32':
|
||||
@@ -48,7 +49,7 @@ if sys.platform == 'win32':
|
||||
from pptx import Presentation
|
||||
from pptx.util import Inches, Pt, Emu
|
||||
from pptx.dml.color import RGBColor
|
||||
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
|
||||
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR, MSO_AUTO_SIZE
|
||||
|
||||
|
||||
# ─── Paths ──────────────────────────────────────────────────────────────────────
|
||||
@@ -83,13 +84,13 @@ COLORS_DARK = {
|
||||
|
||||
# Default font sizes (overridable via spec.font_sizes)
|
||||
DEFAULT_FONT_SIZES = {
|
||||
"title": 36,
|
||||
"subtitle": 18,
|
||||
"slide_title": 24,
|
||||
"body": 16,
|
||||
"bullets": 16,
|
||||
"section": 32,
|
||||
"image_title": 22,
|
||||
"title": 40,
|
||||
"subtitle": 20,
|
||||
"slide_title": 28,
|
||||
"body": 18,
|
||||
"bullets": 18,
|
||||
"section": 36,
|
||||
"image_title": 24,
|
||||
}
|
||||
|
||||
# Slide dimensions (LAYOUT_WIDE = 13.333 x 7.5 inches)
|
||||
@@ -97,7 +98,7 @@ SLIDE_W = Inches(13.333)
|
||||
SLIDE_H = Inches(7.5)
|
||||
|
||||
# Layout constants for two-column slides (text + image)
|
||||
L_MARGIN = Inches(0.6) # left margin (aligns with title)
|
||||
L_MARGIN = Inches(0.7) # left margin (aligns with title)
|
||||
R_MARGIN = Inches(0.3) # right margin
|
||||
COL_GAP = Inches(0.3) # gap between text and image columns
|
||||
CONTENT_W = SLIDE_W - L_MARGIN - R_MARGIN # ~12.43"
|
||||
@@ -254,6 +255,7 @@ def add_text_box(slide, left, top, width, height, text, font_size=Pt(16),
|
||||
txBox = slide.shapes.add_textbox(left, top, width, height)
|
||||
tf = txBox.text_frame
|
||||
tf.word_wrap = True
|
||||
tf.auto_size = MSO_AUTO_SIZE.TEXT_TO_FIT_SHAPE
|
||||
p = tf.paragraphs[0]
|
||||
p.alignment = alignment
|
||||
run = p.add_run()
|
||||
@@ -265,12 +267,32 @@ def add_text_box(slide, left, top, width, height, text, font_size=Pt(16),
|
||||
return txBox
|
||||
|
||||
|
||||
def _fit_bullet_font_size(items, width_emu, height_emu, max_pt):
|
||||
"""Return the largest font size (pt) where all bullets fit in the box."""
|
||||
col_w_pt = width_emu / 12700 # EMU → pt (1pt = 12700 EMU)
|
||||
col_h_pt = height_emu / 12700
|
||||
for fs in range(int(max_pt), 9, -1):
|
||||
space_after = min(8, max(4, fs // 3))
|
||||
line_h = fs * 1.35 + space_after
|
||||
chars_per_line = max(1, int(col_w_pt / (fs * 0.52)))
|
||||
total = sum(max(1, math.ceil((len(str(it)) + 2) / chars_per_line)) * line_h
|
||||
for it in items)
|
||||
if total <= col_h_pt:
|
||||
return fs
|
||||
return 9
|
||||
|
||||
|
||||
def add_bullet_list(slide, left, top, width, height, items, font_size=Pt(16),
|
||||
color_hex="2D3748", bullet_color_hex="1668E3", font_name=None):
|
||||
"""Add a text box with bullet points."""
|
||||
txBox = slide.shapes.add_textbox(left, top, width, height)
|
||||
tf = txBox.text_frame
|
||||
tf.word_wrap = True
|
||||
tf.auto_size = MSO_AUTO_SIZE.TEXT_TO_FIT_SHAPE
|
||||
|
||||
fitted_pt = _fit_bullet_font_size(items, width, height, font_size.pt)
|
||||
fitted_fs = Pt(fitted_pt)
|
||||
space_after_pt = min(8, max(4, fitted_pt // 3))
|
||||
|
||||
for i, item in enumerate(items):
|
||||
if i == 0:
|
||||
@@ -278,19 +300,19 @@ def add_bullet_list(slide, left, top, width, height, items, font_size=Pt(16),
|
||||
else:
|
||||
p = tf.add_paragraph()
|
||||
|
||||
p.space_after = Pt(4)
|
||||
p.space_after = Pt(space_after_pt)
|
||||
p.level = 0
|
||||
|
||||
# Add bullet character manually for reliable rendering
|
||||
bullet_run = p.add_run()
|
||||
bullet_run.text = "● "
|
||||
bullet_run.font.size = font_size
|
||||
bullet_run.font.size = fitted_fs
|
||||
bullet_run.font.color.rgb = hex_to_rgb(bullet_color_hex)
|
||||
bullet_run.font.name = font_name or get_font()
|
||||
|
||||
text_run = p.add_run()
|
||||
text_run.text = str(item)
|
||||
text_run.font.size = font_size
|
||||
text_run.font.size = fitted_fs
|
||||
text_run.font.color.rgb = hex_to_rgb(color_hex)
|
||||
text_run.font.name = font_name or get_font()
|
||||
|
||||
@@ -596,7 +618,7 @@ def make_content_slide(prs, slide_spec, project_dir, workspace_path, colors, is_
|
||||
return slide
|
||||
|
||||
content_y = Inches(1.5) if has_title else Inches(0.5)
|
||||
content_h = SLIDE_H - content_y - Inches(0.3)
|
||||
content_h = SLIDE_H - content_y - Inches(0.4)
|
||||
|
||||
if has_title:
|
||||
add_text_box(slide, L_MARGIN, Inches(0.4), SLIDE_W - L_MARGIN - R_MARGIN, Inches(0.8),
|
||||
|
||||
Reference in New Issue
Block a user