import pandas as pd import streamlit as st import matplotlib.pyplot as plt import matplotlib.font_manager as font_manager import io import base64 import os from datetime import datetime, timedelta import math from matplotlib.patches import FancyBboxPatch from pypinyin import lazy_pinyin, Style from matplotlib.backends.backend_pdf import PdfPages def get_font(size=14):     font_path = "simHei.ttc"     if not os.path.exists(font_path):         font_path = "SimHei.ttf"     return font_manager.FontProperties(fname=font_path, size=size) def get_pinyin_abbr(text):     """获取文本前两个汉字的拼音首字母"""     if not text:         return ""     # 提取前两个汉字     chars = [c for c in text if '\u4e00' <= c <= '\u9fff']     if len(chars) < 2:         chars = chars + [''] * (2 - len(chars))     else:         chars = chars[:2]     # 获取拼音首字母     pinyin_list = lazy_pinyin(chars, style=Style.FIRST_LETTER)     return ''.join(pinyin_list).upper() def process_schedule(file):     try:         date_df = pd.read_excel(file, header=None, skiprows=7, nrows=1, usecols=[3])         date_str = pd.to_datetime(date_df.iloc[0, 0]).strftime('%Y-%m-%d')         base_date = pd.to_datetime(date_str).date()     except:         date_str = datetime.today().strftime('%Y-%m-%d')         base_date = datetime.today().date()          try:         df = pd.read_excel(file, header=9, usecols=[1, 2, 4, 5])         df.columns = ['Hall', 'StartTime', 'EndTime', 'Movie']         df['Hall'] = df['Hall'].ffill()         df.dropna(subset=['StartTime', 'EndTime', 'Movie'], inplace=True)         df['Hall'] = df['Hall'].astype(str).str.extract(r'(\d+号)')         df['StartTime_dt'] = pd.to_datetime(df['StartTime'], format='%H:%M', errors='coerce').apply(             lambda t: t.replace(year=base_date.year, month=base_date.month, day=base_date.day) if pd.notnull(t) else t         )         df['EndTime_dt'] = pd.to_datetime(df['EndTime'], format='%H:%M', errors='coerce').apply(             lambda t: t.replace(year=base_date.year, month=base_date.month, day=base_date.day) if pd.notnull(t) else t         )         df.loc[df['EndTime_dt'] < df['StartTime_dt'], 'EndTime_dt'] += timedelta(days=1)         df = df.sort_values(['Hall', 'StartTime_dt'])                  merged_rows = []         for hall, group in df.groupby('Hall'):             group = group.sort_values('StartTime_dt')             current = None             for _, row in group.iterrows():                 if current is None:                     current = row.copy()                 else:                     if row['Movie'] == current['Movie']:                         current['EndTime_dt'] = row['EndTime_dt']                     else:                         merged_rows.append(current)                         current = row.copy()             if current is not None:                 merged_rows.append(current)                  merged_df = pd.DataFrame(merged_rows)                  # 将开始时间统一提前10分钟,结束时间统一提前5分钟         merged_df['StartTime_dt'] = merged_df['StartTime_dt'] - timedelta(minutes=10)         merged_df['EndTime_dt'] = merged_df['EndTime_dt'] - timedelta(minutes=5)                  merged_df['StartTime_str'] = merged_df['StartTime_dt'].dt.strftime('%H:%M')         merged_df['EndTime_str'] = merged_df['EndTime_dt'].dt.strftime('%H:%M')                  return merged_df[['Hall', 'Movie', 'StartTime_str', 'EndTime_str']], date_str     except:         return None, date_str def create_print_layout(data, date_str):     if data is None or data.empty:         return None          # 创建PNG图像 - 使用A4纸张大小并最大化内容     png_fig = plt.figure(figsize=(8.27, 11.69), dpi=300)     png_ax = png_fig.add_subplot(111)     png_ax.set_axis_off()     # 为PNG设置更小的边距以最大化内容     png_fig.subplots_adjust(left=0.02, right=0.98, top=0.98, bottom=0.02)          # 创建PDF图像 - 使用A4纸张大小     pdf_fig = plt.figure(figsize=(8.27, 11.69), dpi=300)     pdf_ax = pdf_fig.add_subplot(111)     pdf_ax.set_axis_off()     # 为PDF设置更小的边距     pdf_fig.subplots_adjust(left=0.02, right=0.98, top=0.98, bottom=0.02)          # 处理两个图形的共同函数     def process_figure(fig, ax, is_pdf=False):         # Pre-load fonts         date_font = get_font(12)         # 使用更大的字体以最大化利用纸张空间         font_size_multiplier = 1.2         movie_font_size = 14 * font_size_multiplier         hall_font_size = movie_font_size * 0.8         hall_font = get_font(hall_font_size)         movie_font = get_font(movie_font_size)                  ax.text(0.00, 1.00, date_str, fontsize=12 * font_size_multiplier, color='#A9A9A9',                 ha='left', va='top', fontproperties=date_font, transform=ax.transAxes, zorder=2)                  halls = sorted(data['Hall'].unique(), key=lambda h: int(h.replace('号','')) if h else 0)                  total_lines = sum(len(data[data['Hall'] == hall]) for hall in halls) + (len(halls) - 1)         available_height = 0.98 - 0.02  # 增加可用高度         line_spacing = available_height / total_lines if total_lines > 0 else 0.04         y_position = 0.98                  for hall in halls:             hall_data = data[data['Hall'] == hall]             y_block_top = y_position             hall_num = hall.replace("号", "")             hall_text = f"${hall_num}^{{\\#}}$"             movie_count = 1                          for _, row in hall_data.iterrows():                 ax.text(0.03, y_position, hall_text if movie_count == 1 else "",                         fontsize=hall_font_size, fontweight='bold',                         ha='left', va='top', fontproperties=hall_font,                         transform=ax.transAxes, zorder=2)                                  # 获取电影名前两个字的拼音首字母                 pinyin_abbr = get_pinyin_abbr(row['Movie'])                                  # 电影名称左对齐,限制在0.2到0.6的区域内                 ax.text(0.20, y_position, f"{movie_count}. {pinyin_abbr} {row['Movie']}",                         fontsize=movie_font_size, ha='left', va='top', fontproperties=movie_font,                         transform=ax.transAxes, zorder=2, clip_on=True,                         bbox=dict(boxstyle="square,pad=0.0", fc="none", ec="none", alpha=0))                                  # 时间信息右对齐,固定在0.95位置                 ax.text(0.95, y_position, f"{row['StartTime_str']} - {row['EndTime_str']}",                         fontsize=movie_font_size, ha='right', va='top', fontproperties=movie_font,                         transform=ax.transAxes, zorder=2)                                  y_position -= line_spacing                 movie_count += 1                          y_block_bottom = y_position             y_position -= line_spacing             rect = FancyBboxPatch((0.03, y_block_bottom), 0.94, y_block_top - y_block_bottom,                                 boxstyle="round,pad=0.005,rounding_size=0.005",                                 edgecolor='gray', facecolor='white',                                 linewidth=0.8, zorder=1, transform=ax.transAxes)             ax.add_patch(rect)          # 处理PNG图形     process_figure(png_fig, png_ax)          # 处理PDF图形     process_figure(pdf_fig, pdf_ax, is_pdf=True)          # 保存PNG图像 - 使用最小边距以最大化内容     png_buffer = io.BytesIO()     png_fig.savefig(png_buffer, format='png', bbox_inches='tight', pad_inches=0.05)     png_buffer.seek(0)     image_base64 = base64.b64encode(png_buffer.getvalue()).decode()     plt.close(png_fig)          # 保存PDF文件 - 使用最小边距     pdf_buffer = io.BytesIO()     with PdfPages(pdf_buffer) as pdf:         pdf.savefig(pdf_fig, bbox_inches='tight', pad_inches=0.05)     pdf_buffer.seek(0)     pdf_base64 = base64.b64encode(pdf_buffer.getvalue()).decode()     plt.close(pdf_fig)          return {         'png': f"data:image/png;base64,{image_base64}",         'pdf': f"data:application/pdf;base64,{pdf_base64}"     } def display_pdf(base64_pdf):     # 在Streamlit中显示PDF     pdf_display = f"""         """     return pdf_display st.set_page_config(page_title="LED 屏幕时间表打印", layout="wide") st.title("LED 屏幕时间表打印") uploaded_file = st.file_uploader("选择打开【放映时间核对表.xls】文件", accept_multiple_files=False, type=["xls"]) if uploaded_file:     with st.spinner("文件正在处理中,请稍候..."):         schedule, date_str = process_schedule(uploaded_file)         if schedule is not None:             output = create_print_layout(schedule, date_str)                          # 创建选项卡以切换PNG和PDF视图             tab1, tab2 = st.tabs(["PDF 预览", "PNG 预览"])                          with tab1:                 st.markdown(display_pdf(output['pdf']), unsafe_allow_html=True)                          with tab2:                 st.image(output['png'], use_container_width=True)         else:             st.error("无法处理文件,请检查文件格式或内容是否正确。")