如何在Streamlit中精准计算自定义st.markdown行数以统一高度?
实现高度一致的Streamlit自定义Markdown组件
我正在构建两个自定义st.markdown组件,需要给它们定义高度、宽度和背景色,核心要求是两者高度必须一致,且等于内容行数最多的那个组件的高度(比如一个内容占10行,另一个占7行,两个都要设为10行的高度)。
现有Markdown组件示例代码
目前的组件实现模板如下:
st.markdown( f""" <div style="background-color:#e7efe0;margin: 10px; padding:10px;border-radius:10px; height:{max_lines_count}em; width: 35em; margin:0 auto"> <p style="color:#333333;text-align:left;font-size:12px;">Text here</p> </div> """, unsafe_allow_html=True )
(注:原代码中闭合标签写错了,已修正为</p>)
现有行数计算算法的问题
我之前尝试通过估算单行可容纳的字符数来计算行数,比如设定单行最多22个字符,超了就换行。函数代码如下:
def count_lines(sentence, max_num=22): words = sentence.split() lines = 1 current_line_length = 0 for word in words: word_length = len(word) if current_line_length == 0: current_line_length += word_length elif current_line_length + 1 + word_length <= max_num: current_line_length += 1 + word_length else: lines += 1 current_line_length = word_length return lines
但这个方法误差很大——不同字符的宽度不一样,比如逗号“,”和字母“m”的宽度差很多,按字符数算出来的行数和实际渲染的行数对不上。所以需要找到能精准计算字符串像素宽度的方法,当字符串像素宽度超过Markdown组件的宽度时判定换行,从而算出准确的行数。
精准计算行数的两种方案
方案1:用Python的PIL库计算文本像素宽度
通过PIL模拟前端的字体渲染,计算文本的实际像素宽度,再结合组件的像素宽度来统计行数。
from PIL import ImageFont def calculate_text_width(text, font_path=None, font_size=12): # 优先使用指定字体,否则用系统默认字体 if font_path: font = ImageFont.truetype(font_path, font_size) else: font = ImageFont.load_default() # 返回文本的像素宽度 width, _ = font.getsize(text) return width def count_actual_lines(sentence, container_width_px=420, font_size=12, font_path=None): words = sentence.split() if not words: return 1 lines = 1 current_line_width = 0 font = ImageFont.truetype(font_path, font_size) if font_path else ImageFont.load_default() # 计算单个空格的像素宽度 space_width, _ = font.getsize(" ") for word in words: word_width, _ = font.getsize(word) if current_line_width == 0: # 处理第一行的第一个单词 if word_width > container_width_px: # 单个单词超长,按容器宽度拆分多行 lines += word_width // container_width_px current_line_width = word_width % container_width_px else: current_line_width = word_width else: # 加上空格后的总宽度 total_width = current_line_width + space_width + word_width if total_width <= container_width_px: current_line_width = total_width else: lines += 1 if word_width > container_width_px: lines += word_width // container_width_px current_line_width = word_width % container_width_px else: current_line_width = word_width return lines
使用说明:
container_width_px对应组件的实际像素宽度,示例中35em(字号12px)= 12*35=420px- 要尽量匹配前端组件使用的字体,比如前端用Arial,就传入对应的字体文件路径,否则计算结果会有偏差
- 已经处理了单个单词超过容器宽度的极端情况
方案2:前端JS动态调整(最精准)
既然最终是在浏览器中渲染,直接用JS动态计算组件高度是最准确的,不需要在Python端提前算行数。
import streamlit as st from streamlit.components.v1 import html def render_synced_markdown(text1, text2): # 渲染两个带ID的容器 html_content = f""" <div id="box1" style="background-color:#e7efe0;margin:10px; padding:10px;border-radius:10px; width:35em; margin:0 auto"> <p style="color:#333333;text-align:left;font-size:12px;">{text1}</p> </div> <div id="box2" style="background-color:#e7efe0;margin:10px; padding:10px;border-radius:10px; width:35em; margin:0 auto"> <p style="color:#333333;text-align:left;font-size:12px;">{text2}</p> </div> <script> // 获取两个容器的实际渲染高度 const h1 = document.getElementById('box1').offsetHeight; const h2 = document.getElementById('box2').offsetHeight; const maxH = Math.max(h1, h2); // 同步设置两个容器的高度为最大值 document.getElementById('box1').style.height = maxH + 'px'; document.getElementById('box2').style.height = maxH + 'px'; </script> """ # 预留padding和margin的高度,避免内容被截断 html(html_content, height=maxH + 40)
这个方案完全依赖浏览器的实际渲染结果,不存在字体、字符宽度的误差,实现起来也更简单高效。
内容的提问来源于stack exchange,提问作者Tatevik Avetisyan
相关产品推荐
相关产品推荐

