03번 B' 정상 동작: 가로 3단 카드 + overflow 해소 + 하단 균형
- block_assembler B': 카드 3개 이상 + 이미지 없음 → 가로(row) 배치 - block_assembler B': section title이 카드 제목, D1은 카드 내 bold 불릿 - block_assembler: overflow:auto → overflow:hidden, [핵심요약:] 마커 필터 - block_assembler: \x01 바이트 수정 - pipeline: Selenium 실측 기반 zone 간 재배분 (allocated-scrollHeight로 slack 계산) - pipeline: surplus 최대 50%만 이전 (하단 최소 공간 보장) - pipeline: bottom_left/bottom_right → Selenium bottom zone 매핑 - kei_client: 상단은 팝업 대상 제외, 하단에서만 팝업 분리 결과: 02번/03번 모두 overflow 없이 정상 출력 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -590,6 +590,8 @@ def _assemble_slide_html_type_b(ctx: "PipelineContext", title_text: str = "") ->
|
||||
continue
|
||||
if re.search(r'\[이미지:', stripped) or re.match(r'^!\[', stripped):
|
||||
continue
|
||||
if re.search(r'\[핵심요약:', stripped):
|
||||
continue
|
||||
content_lines.append(stripped)
|
||||
|
||||
popup_html = _popup_links_html(popup_titles, font_size)
|
||||
@@ -1012,11 +1014,17 @@ def _assemble_slide_html_type_b_prime(ctx: "PipelineContext", title_text: str =
|
||||
continue
|
||||
if re.search(r'\[이미지:', stripped) or re.match(r'^!\[', stripped):
|
||||
continue
|
||||
if re.search(r'\[핵심요약:', stripped):
|
||||
continue
|
||||
content_lines.append(stripped)
|
||||
|
||||
popup_html = _popup_links_html(popup_titles, font_size)
|
||||
|
||||
# 소제목(### 또는 D1:) + 불릿(D2:)을 카드형으로 분리
|
||||
# B': ### (section title) = 카드 제목. D1/D2는 카드 내부 불릿.
|
||||
# ###이 있으면 카드는 section 단위. D1은 카드 안의 bold 불릿.
|
||||
# ###이 없으면 D1이 카드 제목 (02번 방식).
|
||||
has_section_titles = any(line.startswith("### ") for line in content_lines)
|
||||
|
||||
sections = []
|
||||
current_section = ("", [])
|
||||
for line in content_lines:
|
||||
@@ -1025,11 +1033,15 @@ def _assemble_slide_html_type_b_prime(ctx: "PipelineContext", title_text: str =
|
||||
sections.append(current_section)
|
||||
current_section = (line.lstrip("# ").strip(), [])
|
||||
elif re.match(r'^D1:\s*', line):
|
||||
# D1 = 1단 불릿 = 소제목 (카드 제목)
|
||||
title_text = re.sub(r'^D1:\s*', '', line).lstrip("• ")
|
||||
if current_section[0] or current_section[1]:
|
||||
sections.append(current_section)
|
||||
current_section = (_bold(title_text, rn), [])
|
||||
if has_section_titles:
|
||||
# ### 카드 안의 bold 불릿
|
||||
current_section[1].append(f'<strong>{_bold(title_text, rn)}</strong>')
|
||||
else:
|
||||
# 02번 방식: D1이 카드 제목
|
||||
if current_section[0] or current_section[1]:
|
||||
sections.append(current_section)
|
||||
current_section = (_bold(title_text, rn), [])
|
||||
elif re.match(r'^D[2-9]:\s*', line):
|
||||
# D2+ = 하위 불릿 = 본문
|
||||
clean = re.sub(r'^D[2-9]:\s*', '', line).lstrip("• ")
|
||||
@@ -1055,17 +1067,13 @@ def _assemble_slide_html_type_b_prime(ctx: "PipelineContext", title_text: str =
|
||||
card_gap = max(3, int(font_size * 0.4))
|
||||
indent_body = int(font_size * 1.2)
|
||||
|
||||
# B': 상단이 popup 대상이면 소제목만 유지, 하위 불릿 제거
|
||||
top_is_popup = rn in popup_roles
|
||||
# B': 상단은 핵심이므로 항상 불릿 표시 (팝업 대상 아님)
|
||||
|
||||
bullets = ""
|
||||
if len(sections) > 1 and sections[0][0]:
|
||||
for ci, (sec_title, sec_items) in enumerate(sections):
|
||||
bg, text_color = _card_colors[ci % len(_card_colors)]
|
||||
if top_is_popup:
|
||||
items_html = ""
|
||||
else:
|
||||
items_html = "".join(
|
||||
items_html = "".join(
|
||||
f'<div style="padding-left:{indent_body}px;margin-bottom:1px;">'
|
||||
f'<span style="color:{text_color};font-size:{font_size-1}px;line-height:1.5;">• {item}</span></div>'
|
||||
for item in sec_items
|
||||
@@ -1112,15 +1120,28 @@ def _assemble_slide_html_type_b_prime(ctx: "PipelineContext", title_text: str =
|
||||
|
||||
topic_title = _bold(topic_title_from_section or rn, rn)
|
||||
|
||||
top_html = (
|
||||
f'<div style="position:relative;height:100%;padding:{gap_small}px;box-sizing:border-box;'
|
||||
f'display:flex;flex-direction:column;justify-content:space-between;">'
|
||||
f'{popup_html}'
|
||||
f'<div style="font-weight:700;font-size:{font_size+1}px;color:#1a365d;margin-bottom:4px;">{topic_title}</div>'
|
||||
f'<div style="display:flex;gap:{max(6, int(font_size*0.8))}px;align-items:flex-start;flex:1;">'
|
||||
f'<div style="flex:1;font-size:{font_size}px;line-height:1.55;color:#333;">{bullets}</div>'
|
||||
f'{img_block}</div></div>'
|
||||
)
|
||||
# B': 카드 3개 이상 + 이미지 없음 → 가로 배치
|
||||
card_count = len(sections) if len(sections) > 1 and sections[0][0] else 0
|
||||
use_row = card_count >= 3 and not (has_image and img_html)
|
||||
|
||||
if use_row:
|
||||
top_html = (
|
||||
f'<div style="position:relative;height:100%;padding:{gap_small}px;box-sizing:border-box;'
|
||||
f'display:flex;flex-direction:column;">'
|
||||
f'{popup_html}'
|
||||
f'<div style="font-weight:700;font-size:{font_size+1}px;color:#1a365d;margin-bottom:4px;">{topic_title}</div>'
|
||||
f'<div style="display:flex;flex-direction:row;gap:{card_gap}px;flex:1;">{bullets}</div></div>'
|
||||
)
|
||||
else:
|
||||
top_html = (
|
||||
f'<div style="position:relative;height:100%;padding:{gap_small}px;box-sizing:border-box;'
|
||||
f'display:flex;flex-direction:column;justify-content:space-between;">'
|
||||
f'{popup_html}'
|
||||
f'<div style="font-weight:700;font-size:{font_size+1}px;color:#1a365d;margin-bottom:4px;">{topic_title}</div>'
|
||||
f'<div style="display:flex;gap:{max(6, int(font_size*0.8))}px;align-items:flex-start;flex:1;">'
|
||||
f'<div style="flex:1;font-size:{font_size}px;line-height:1.55;color:#333;">{bullets}</div>'
|
||||
f'{img_block}</div></div>'
|
||||
)
|
||||
|
||||
# ── 하단: normalized.sections에서 직접 매핑 ──
|
||||
bottom_title = ""
|
||||
@@ -1200,6 +1221,8 @@ def _assemble_slide_html_type_b_prime(ctx: "PipelineContext", title_text: str =
|
||||
clean_plain = re.sub(r'<[^>]+>', '', clean).strip()
|
||||
if clean_plain in table_cell_texts or clean_plain == "➠":
|
||||
continue
|
||||
if re.search(r'\[핵심요약:', clean):
|
||||
continue
|
||||
if clean:
|
||||
clean = _bold(clean, rn)
|
||||
_pad = bl_indent * depth
|
||||
@@ -1208,7 +1231,7 @@ def _assemble_slide_html_type_b_prime(ctx: "PipelineContext", title_text: str =
|
||||
bul += f'<div style="padding-left:{_pad}px;font-size:{fs}px;margin-bottom:2px;{weight}">• {clean}</div>\n'
|
||||
|
||||
bl_html = (
|
||||
f'<div style="height:100%;padding:{gap_small}px;box-sizing:border-box;overflow-y:auto;">'
|
||||
f'<div style="height:100%;padding:{gap_small}px;box-sizing:border-box;overflow:hidden;">'
|
||||
f'<div style="font-weight:700;font-size:{font_size+1}px;color:#1a365d;margin-bottom:4px;">{_bold(sub_title, rn)}</div>'
|
||||
f'{table_html_bl}'
|
||||
f'<div style="line-height:1.55;color:#333;">{bul}</div></div>'
|
||||
@@ -1232,6 +1255,8 @@ def _assemble_slide_html_type_b_prime(ctx: "PipelineContext", title_text: str =
|
||||
depth = int(dm.group(1))
|
||||
stripped = re.sub(r'^D\d+:\s*', '', stripped)
|
||||
clean = stripped.lstrip("- ").lstrip("• ")
|
||||
if re.search(r'\[핵심요약:', clean):
|
||||
continue
|
||||
if clean:
|
||||
clean = _bold(clean, rn)
|
||||
_pad = bl_indent * depth
|
||||
|
||||
Reference in New Issue
Block a user