Spaces:
Sleeping
Sleeping
| import re | |
| import subprocess | |
| wanted_variations = [] | |
| skip_variations = ["horizontal_group_bar_chart_13"] | |
| def parse_translate(transform_str): | |
| """解析SVG transform属性中的translate值""" | |
| if not transform_str: | |
| return 0.0, 0.0 | |
| total_x, total_y = 0.0, 0.0 | |
| # 查找所有translate(x, y)或translate(x)模式 | |
| translate_matches = re.finditer(r'translate\(\s*([-\d.]+)(?:\s*,\s*([-\d.]+))?\s*\)', transform_str) | |
| for match in translate_matches: | |
| x = float(match.group(1)) | |
| y = float(match.group(2)) if match.group(2) else 0.0 | |
| total_x += x | |
| total_y += y | |
| return total_x, total_y | |
| def parse_percentage(value_str, reference): | |
| """解析SVG中的百分比值""" | |
| if not value_str: | |
| return 0.0 | |
| value_str = str(value_str).strip() | |
| # 处理百分比 | |
| if '%' in value_str: | |
| percentage = float(value_str.replace('%', '')) | |
| return reference * percentage / 100.0 | |
| # 处理像素值 | |
| if 'px' in value_str: | |
| return float(value_str.replace('px', '')) | |
| # 处理纯数字 | |
| try: | |
| return float(value_str) | |
| except ValueError: | |
| return 0.0 | |
| def get_accumulated_transform(elem, root): | |
| # 获取从根元素到当前元素的所有transform累加 | |
| total_dx, total_dy = 0.0, 0.0 | |
| current = elem | |
| # 存储父元素路径以便从外向内累积变换 | |
| path = [] | |
| while current is not None: | |
| path.append(current) | |
| if current == root: | |
| break | |
| current = current.getparent() | |
| # 从外向内累积变换(从根元素到目标元素) | |
| for current in reversed(path): | |
| transform = current.get("transform") | |
| if transform: | |
| dx, dy = parse_translate(transform) | |
| total_dx += dx | |
| total_dy += dy | |
| return total_dx, total_dy | |
| def get_text_bounding_box(text_elem, svg_width, svg_height): | |
| """获取文本元素的精确边界框,考虑各种对齐方式和基线属性""" | |
| # 获取SVG的根元素 | |
| root = text_elem.getroottree().getroot() | |
| # 获取累积的变换信息 | |
| dx, dy = get_accumulated_transform(text_elem, root) | |
| # 基本位置信息 | |
| x = parse_percentage(text_elem.get('x', '0'), svg_width) + dx | |
| y = parse_percentage(text_elem.get('y', '0'), svg_height) + dy | |
| # 获取字体大小 | |
| font_size_str = text_elem.get('font-size', '16') | |
| font_size = float(font_size_str.replace('px', '')) if 'px' in font_size_str else float(font_size_str) | |
| # 获取文本内容及长度估算 | |
| text_content = text_elem.text or "" | |
| # 更精确的宽度估算 - 实际应用中可能需要根据字体特性调整 | |
| text_width = font_size * 0.6 * len(text_content) | |
| text_height = font_size * 1.2 # 考虑行高 | |
| # 处理文本对齐方式 | |
| text_anchor = text_elem.get('text-anchor', 'start') | |
| if text_anchor == 'start': | |
| text_left = x | |
| text_right = x + text_width | |
| elif text_anchor == 'middle': | |
| text_left = x - text_width / 2 | |
| text_right = x + text_width / 2 | |
| elif text_anchor == 'end': | |
| text_left = x - text_width | |
| text_right = x | |
| else: # 默认为start | |
| text_left = x | |
| text_right = x + text_width | |
| # 处理基线属性 | |
| dominant_baseline = text_elem.get('dominant-baseline', 'auto') | |
| if dominant_baseline == 'hanging': | |
| # 悬挂基线,文本从y坐标开始向下展示 | |
| text_top = y | |
| text_bottom = y + text_height | |
| elif dominant_baseline == 'middle': | |
| # 中间基线,文本在y坐标上下对称分布 | |
| text_top = y - text_height / 2 | |
| text_bottom = y + text_height / 2 | |
| else: # 默认为alphabetic或auto | |
| # 字母基线,文本基线在y坐标上 | |
| text_top = y - 0.8 * text_height | |
| text_bottom = y + 0.2 * text_height | |
| return { | |
| 'x1': text_left, | |
| 'y1': text_top, | |
| 'x2': text_right, | |
| 'y2': text_bottom, | |
| 'width': text_right - text_left, | |
| 'height': text_bottom - text_top, | |
| 'content': text_content | |
| } | |
| def check_text_overlap(tree): | |
| """检查SVG中所有文本元素是否存在重叠""" | |
| root = tree.getroot() | |
| svg_width = float(root.get('width', '0').replace('px', '')) | |
| svg_height = float(root.get('height', '0').replace('px', '')) | |
| # 收集所有文本元素的边界框 | |
| text_boxes = [] | |
| text_elems = [] # 存储对应的文本元素 | |
| for text_elem in root.xpath("//text | //*[local-name()='text']"): | |
| # 检查文本元素或其父元素是否存在rotate变换 | |
| if has_rotate_transform(text_elem, root): | |
| continue # 跳过有rotate变换的文本元素 | |
| bbox = get_text_bounding_box(text_elem, svg_width, svg_height) | |
| text_boxes.append(bbox) | |
| text_elems.append(text_elem) | |
| # 检查任意两个文本框是否重叠 | |
| overlapping_pairs = [] | |
| for i, box1 in enumerate(text_boxes): | |
| for j, box2 in enumerate(text_boxes[i+1:], i+1): | |
| # 检查两个盒子是否重叠 | |
| if (box1['x1'] < box2['x2'] and box1['x2'] > box2['x1'] and | |
| box1['y1'] < box2['y2'] and box1['y2'] > box2['y1']): | |
| # 计算重叠区域的大小 | |
| overlap_width = min(box1['x2'], box2['x2']) - max(box1['x1'], box2['x1']) | |
| overlap_height = min(box1['y2'], box2['y2']) - max(box1['y1'], box2['y1']) | |
| overlap_area = overlap_width * overlap_height | |
| # 计算重叠比例 | |
| min_area = min(box1['width'] * box1['height'], box2['width'] * box2['height']) | |
| overlap_ratio = overlap_area / min_area if min_area > 0 else 0 | |
| # 如果重叠比例超过阈值,记录这对重叠文本 | |
| if overlap_ratio > 0.25: # 可以根据需要调整阈值 | |
| overlapping_pairs.append({ | |
| 'text1': box1['content'], | |
| 'text2': box2['content'], | |
| 'overlap_ratio': overlap_ratio, | |
| 'box1': box1, | |
| 'box2': box2, | |
| 'elem1': text_elems[i], # 保存对应的文本元素 | |
| 'elem2': text_elems[j] | |
| }) | |
| return overlapping_pairs | |
| import os | |
| import json | |
| import shutil | |
| import argparse | |
| from multiprocessing import Pool, Manager, Lock | |
| from tqdm import tqdm | |
| from lxml import etree | |
| def has_image_tag(svg_content): | |
| """检查SVG内容是否包含<image标签""" | |
| return "<image" in svg_content | |
| def fix_duplicate_xmlns(svg_path): | |
| """修复SVG文件中重复的命名空间声明""" | |
| try: | |
| with open(svg_path, 'r', encoding='utf-8') as f: | |
| content = f.read() | |
| import re | |
| changed = False | |
| # 检查并删除ns0:xlink属性 | |
| if 'ns0:xlink=' in content: | |
| # 删除ns0:xlink="..."的模式 | |
| content = re.sub(r'\s+ns0:xlink\s*=\s*["\'][^"\']*["\']', '', content) | |
| changed = True | |
| # 检查并删除其他ns*:xlink变体 | |
| if re.search(r'ns\d+:xlink\s*=', content): | |
| content = re.sub(r'\s+ns\d+:xlink\s*=\s*["\'][^"\']*["\']', '', content) | |
| changed = True | |
| # 检查并删除xmlns:ns*声明 | |
| if re.search(r'xmlns:ns\d*\s*=', content): | |
| content = re.sub(r'\s+xmlns:ns\d*\s*=\s*["\'][^"\']*["\']', '', content) | |
| changed = True | |
| # 检查并删除重复的xmlns="http://www.w3.org/2000/svg"声明 | |
| xmlns_count = len(re.findall(r'xmlns\s*=\s*["\']http://www\.w3\.org/2000/svg["\']', content)) | |
| if xmlns_count > 1: | |
| # 保留第一个,删除其他的 | |
| first_found = False | |
| def replace_xmlns(match): | |
| nonlocal first_found | |
| if not first_found: | |
| first_found = True | |
| return match.group(0) # 保留第一个 | |
| else: | |
| return '' # 删除其他的 | |
| content = re.sub(r'\s+xmlns\s*=\s*["\']http://www\.w3\.org/2000/svg["\']', replace_xmlns, content) | |
| changed = True | |
| if changed: | |
| # 清理多余的空格 | |
| content = re.sub(r'\s+', ' ', content) | |
| content = re.sub(r'\s+>', '>', content) | |
| # 写回文件 | |
| with open(svg_path, 'w', encoding='utf-8') as f: | |
| f.write(content) | |
| return True # 表示文件被修改了 | |
| return False # 表示文件没有被修改 | |
| except Exception as e: | |
| print(f"修复SVG命名空间失败 {svg_path}: {e}") | |
| return False | |
| def process_single_folder(args): | |
| """处理单个文件夹""" | |
| # 解包参数,兼容keep_all | |
| if len(args) == 5: | |
| folder_path, output_dir, counter, lock, keep_all = args | |
| else: | |
| folder_path, output_dir, counter, lock = args | |
| keep_all = False | |
| # 先检查SVG是否存在 | |
| svg_path = os.path.join(folder_path, "chart.svg") | |
| png_path = os.path.join(folder_path, "chart.png") | |
| if not os.path.exists(svg_path): | |
| print(f"跳过文件夹 {folder_path},缺少文件 chart.svg") | |
| return None | |
| # 检查并补全SVG根节点属性,并修正image标签的xlink:href | |
| try: | |
| # 先修复可能的重复命名空间声明 | |
| fix_duplicate_xmlns(svg_path) | |
| tree = etree.parse(svg_path) | |
| root = tree.getroot() | |
| changed = False | |
| # 补全根节点命名空间 | |
| if root.get("xmlns") != "http://www.w3.org/2000/svg": | |
| root.set("xmlns", "http://www.w3.org/2000/svg") | |
| changed = True | |
| # 检查xlink命名空间声明(使用正确的命名空间格式) | |
| xlink_ns = "{http://www.w3.org/2000/xmlns/}xlink" | |
| if root.get(xlink_ns) != "http://www.w3.org/1999/xlink": | |
| root.set(xlink_ns, "http://www.w3.org/1999/xlink") | |
| changed = True | |
| # 修正所有image标签的xlink:href和href | |
| nsmap = root.nsmap.copy() if root.nsmap else {} | |
| if None in nsmap: | |
| nsmap["svg"] = nsmap.pop(None) | |
| for image in root.xpath('.//svg:image | .//image', namespaces=nsmap): | |
| href = image.get('href') | |
| xlink_href = image.get('{http://www.w3.org/1999/xlink}href') | |
| # 如果只有href没有xlink:href,则补充 | |
| if href and not xlink_href: | |
| image.set('{http://www.w3.org/1999/xlink}href', href) | |
| changed = True | |
| # 如果只有xlink:href没有href,也补充href | |
| if xlink_href and not href: | |
| image.set('href', xlink_href) | |
| changed = True | |
| # 如果都没有,尝试找data-href等 | |
| if not href and not xlink_href: | |
| for key in image.attrib: | |
| if 'href' in key: | |
| val = image.attrib[key] | |
| image.set('{http://www.w3.org/1999/xlink}href', val) | |
| image.set('href', val) | |
| changed = True | |
| break | |
| if changed: | |
| tree.write(svg_path, encoding="utf-8", xml_declaration=True) | |
| except Exception as e: | |
| print(f"SVG命名空间检查/修正失败: {svg_path},错误: {e}") | |
| # 如果SVG存在但PNG不存在,自动转换 | |
| if not os.path.exists(png_path): | |
| try: | |
| subprocess.run([ | |
| 'rsvg-convert', | |
| '-f', 'png', | |
| '-o', png_path, | |
| '--dpi-x', '300', | |
| '--dpi-y', '300', | |
| '--background-color', '#ffffff', | |
| svg_path | |
| ], check=True) | |
| print(f"已自动将SVG转为PNG: {svg_path} -> {png_path}") | |
| except Exception as e: | |
| print(f"SVG转PNG失败: {svg_path},错误: {e}") | |
| return { | |
| "original_path": folder_path, | |
| "reason": "svg_to_png_failed", | |
| "discarded": True | |
| } | |
| # 再检查其他必需文件 | |
| for file in ["data.json", "info.json"]: | |
| if not os.path.exists(os.path.join(folder_path, file)): | |
| print(f"跳过文件夹 {folder_path},缺少文件 {file}") | |
| return None | |
| # 如果keep_all为True,直接复制并保留,无需任何判断 | |
| if keep_all: | |
| info_path = os.path.join(folder_path, "info.json") | |
| with open(info_path, 'r') as f: | |
| info_data = json.load(f) | |
| chart_variation = info_data.get("chart_variation", "") | |
| # 转换metadata | |
| new_info = { | |
| "chart_variation": info_data.get("chart_variation", ""), | |
| "image_mode": info_data.get("image_mode", ""), | |
| "background_color": info_data.get("background_color", ""), | |
| "chart_type": info_data.get("chart_type", "") | |
| } | |
| original_data_source = info_data.get("data_source") | |
| if isinstance(original_data_source, str) and original_data_source: | |
| new_info["data_source"] = original_data_source.split('/')[-1] | |
| else: | |
| new_info["data_source"] = "" | |
| title_to_chart = info_data.get("title_to_chart", "") | |
| image_to_chart = info_data.get("image_to_chart", "") | |
| if image_to_chart is None or image_to_chart == "none" or str(image_to_chart).lower() == "none": | |
| image_to_chart = "none" | |
| new_info["layout_template"] = f"{title_to_chart}_{image_to_chart}" | |
| with lock: | |
| folder_id = counter.value | |
| counter.value += 1 | |
| new_folder_name = f"{folder_id:08d}" | |
| new_folder_path = os.path.join(output_dir, new_folder_name) | |
| os.makedirs(new_folder_path, exist_ok=True) | |
| for file in ["chart.svg", "chart.png", "data.json", "info.json"]: | |
| src_file = os.path.join(folder_path, file) | |
| dst_file = os.path.join(new_folder_path, file) | |
| shutil.copy2(src_file, dst_file) | |
| with open(os.path.join(new_folder_path, "info.json"), 'w') as f: | |
| json.dump(new_info, f, indent=2) | |
| try: | |
| chart_variation_to_log = new_info.get("chart_variation", "") | |
| layout_template_to_log = new_info.get("layout_template", "") | |
| chart_variation_log_path = os.path.join(output_dir, "chart_variations.txt") | |
| layout_template_log_path = os.path.join(output_dir, "layout_templates.txt") | |
| with open(chart_variation_log_path, 'a', encoding='utf-8') as cv_file: | |
| cv_file.write(f"{chart_variation_to_log}\n") | |
| with open(layout_template_log_path, 'a', encoding='utf-8') as lt_file: | |
| lt_file.write(f"{layout_template_to_log}\n") | |
| except Exception as e: | |
| print(f"警告:无法写入日志文件到 {output_dir}。chart_variation: {chart_variation_to_log}, layout_template: {layout_template_to_log}。错误: {e}") | |
| return { | |
| "original_path": folder_path, | |
| "new_path": new_folder_path, | |
| "overlap_rate": 0, | |
| "discarded": False | |
| } | |
| # 新增:检查SVG文件大小 | |
| svg_path = os.path.join(folder_path, "chart.svg") | |
| try: | |
| svg_file_size = os.path.getsize(svg_path) | |
| if svg_file_size > 15 * 1024 * 1024: # 10MB | |
| print(f"跳过文件夹 {folder_path},SVG文件大小 ({svg_file_size / (1024*1024):.2f}MB) 超过15MB") | |
| return { | |
| "original_path": folder_path, | |
| "reason": "svg_too_large", | |
| "discarded": True | |
| } | |
| except OSError as e: | |
| print(f"无法获取SVG文件大小 {svg_path}:{e},跳过此文件夹检查") | |
| return { | |
| "original_path": folder_path, | |
| "reason": "svg_size_check_error", | |
| "discarded": True | |
| } | |
| # 先检查chart_name是否在mapping中 | |
| info_path = os.path.join(folder_path, "info.json") | |
| try: | |
| with open(info_path, 'r') as f: | |
| info_data = json.load(f) | |
| except json.JSONDecodeError as e: | |
| print(f"跳过文件夹 {folder_path},解析 info.json 失败:{e}") | |
| return { | |
| "original_path": folder_path, | |
| "reason": "info_json_decode_error", | |
| "discarded": True | |
| } | |
| # 检查data.json中的数据数量 | |
| data_path = os.path.join(folder_path, "data.json") | |
| try: | |
| with open(data_path, 'r') as f: | |
| data = json.load(f) | |
| if isinstance(data["data"], list) and len(data) <= 2: | |
| print(f"跳过文件夹 {folder_path},data.json中的数据数量不超过2") | |
| return { | |
| "original_path": folder_path, | |
| "reason": "insufficient_data", | |
| "discarded": True | |
| } | |
| except json.JSONDecodeError as e: | |
| print(f"跳过文件夹 {folder_path},解析 data.json 失败:{e}") | |
| return { | |
| "original_path": folder_path, | |
| "reason": "data_json_decode_error", | |
| "discarded": True | |
| } | |
| svg_path = os.path.join(folder_path, "chart.svg") | |
| # 检查SVG文件是否包含image标签 | |
| with open(svg_path, 'r', encoding='utf-8') as f: | |
| svg_content = f.read() | |
| # 如果SVG不包含image标签,归类为丢弃 | |
| if not has_image_tag(svg_content): | |
| print(f"跳过文件夹 {folder_path},SVG不包含image标签") | |
| return { | |
| "original_path": folder_path, | |
| "reason": "no_image_tag", | |
| "discarded": True | |
| } | |
| try: | |
| # 先修复可能的重复命名空间声明 | |
| fix_duplicate_xmlns(svg_path) | |
| tree = etree.parse(svg_path) | |
| except etree.XMLSyntaxError as e: | |
| print(f"跳过文件夹 {folder_path},解析 chart.svg 失败:{e}") | |
| return { | |
| "original_path": folder_path, | |
| "reason": "svg_parse_error", | |
| "discarded": True | |
| } | |
| # 新增:检查最小字体大小 | |
| min_font_size_violation = False | |
| for text_elem in tree.xpath("//text | //*[local-name()='text']"): | |
| font_size_attr = text_elem.get('font-size', '16') # 默认16px | |
| font_size_str = str(font_size_attr) # Ensure it's a string for processing | |
| try: | |
| # Common case: '12px' or '12' | |
| if 'px' in font_size_str: | |
| current_font_size = float(font_size_str.replace('px', '')) | |
| else: | |
| current_font_size = float(font_size_str) | |
| if current_font_size <= 9: | |
| min_font_size_violation = True | |
| break | |
| except ValueError: | |
| # Handle cases where font-size might be non-numeric e.g. "inherit", "medium" | |
| print(f"警告:文件夹 {folder_path} 中的文本元素存在无法解析的font-size值 '{font_size_attr}'。此文本元素的字体大小检查将被跳过。") | |
| pass # Or, depending on policy: min_font_size_violation = True | |
| if min_font_size_violation: | |
| print(f"跳过文件夹 {folder_path},存在字体小于7px的文本。") | |
| return { | |
| "original_path": folder_path, | |
| "reason": "min_font_size_violation", | |
| "discarded": True | |
| } | |
| overlapping_pairs = check_text_overlap(tree) | |
| # 打印重叠文本对信息 | |
| if overlapping_pairs and False: | |
| print(f"\n在文件夹 {folder_path} 中发现重叠文本:") | |
| for i, pair in enumerate(overlapping_pairs, 1): | |
| print(f"重叠对 {i}:") | |
| print(f" 文本1: '{pair['text1']}'") | |
| print(f" 边界框1: x1={pair['box1']['x1']:.1f}, y1={pair['box1']['y1']:.1f}, x2={pair['box1']['x2']:.1f}, y2={pair['box1']['y2']:.1f}, w={pair['box1']['width']:.1f}, h={pair['box1']['height']:.1f}") | |
| # 检查并打印rotate信息 | |
| elem1 = pair['elem1'] | |
| has_rotate1 = has_rotate_transform(elem1, tree.getroot()) | |
| print(f" 是否有rotate变换1: {has_rotate1}") | |
| # 打印原始transform信息 | |
| transform1 = elem1.get("transform", "无transform") | |
| print(f" 原始transform1: {transform1}") | |
| # 打印transform层级链 | |
| print(f" transform链1:") | |
| current = elem1 | |
| level = 1 | |
| root = tree.getroot() | |
| while current is not None and current != root.getparent(): | |
| transform = current.get("transform") | |
| if transform: | |
| print(f" 层级{level}: {transform}") | |
| level += 1 | |
| current = current.getparent() | |
| print(f" 文本2: '{pair['text2']}'") | |
| print(f" 边界框2: x1={pair['box2']['x1']:.1f}, y1={pair['box2']['y1']:.1f}, x2={pair['box2']['x2']:.1f}, y2={pair['box2']['y2']:.1f}, w={pair['box2']['width']:.1f}, h={pair['box2']['height']:.1f}") | |
| # 检查并打印rotate信息 | |
| elem2 = pair['elem2'] | |
| has_rotate2 = has_rotate_transform(elem2, tree.getroot()) | |
| print(f" 是否有rotate变换2: {has_rotate2}") | |
| # 打印原始transform信息 | |
| transform2 = elem2.get("transform", "无transform") | |
| print(f" 原始transform2: {transform2}") | |
| # 打印transform层级链 | |
| print(f" transform链2:") | |
| current = elem2 | |
| level = 1 | |
| while current is not None and current != root.getparent(): | |
| transform = current.get("transform") | |
| if transform: | |
| print(f" 层级{level}: {transform}") | |
| level += 1 | |
| current = current.getparent() | |
| print(f" 重叠比例: {pair['overlap_ratio']:.2f}") | |
| # 计算重叠率 | |
| # 首先获取所有文本元素数量 | |
| root = tree.getroot() | |
| total_texts = len(root.xpath("//text | //*[local-name()='text']")) | |
| # 计算有多少不同的文本元素存在重叠 | |
| overlapping_texts = set() | |
| for pair in overlapping_pairs: | |
| overlapping_texts.add(pair['text1']) | |
| overlapping_texts.add(pair['text2']) | |
| overlap_rate = len(overlapping_texts) * 100 / total_texts if total_texts > 0 else 0 | |
| # 判断是否丢弃 | |
| if info_data.get("chart_variation", "") not in wanted_variations: | |
| is_discarded = (overlap_rate > 5 or len(overlapping_pairs) > 3) | |
| elif info_data.get("chart_variation", "") in skip_variations: | |
| is_discarded = True | |
| else: | |
| is_discarded = (overlap_rate > 15 or len(overlapping_pairs) > 5) | |
| # 如果需要丢弃,直接返回不复制文件 | |
| if is_discarded: | |
| return { | |
| "original_path": folder_path, | |
| "reason": "text_overlap", | |
| "discarded": True | |
| } | |
| # 只处理需要保留的文件 | |
| # 处理info.json - 因为已经在前面读取过,这里可以直接使用info_data | |
| # 转换metadata | |
| new_info = { | |
| "chart_variation": info_data.get("chart_variation", ""), | |
| "image_mode": info_data.get("image_mode", ""), | |
| "background_color": info_data.get("background_color", "") | |
| } | |
| # 新增:保留并处理data_source | |
| original_data_source = info_data.get("data_source") # Get value, could be None | |
| if isinstance(original_data_source, str) and original_data_source: # Check if it's a non-empty string | |
| new_info["data_source"] = original_data_source.split('/')[-1] | |
| else: | |
| new_info["data_source"] = "" # Default to empty string if not found, None, or empty | |
| # 处理layout_template | |
| title_to_chart = info_data.get("title_to_chart", "") | |
| image_to_chart = info_data.get("image_to_chart", "") | |
| # 如果image_to_chart是none,改成"none"字符串 | |
| if image_to_chart is None or image_to_chart == "none" or image_to_chart.lower() == "none": | |
| image_to_chart = "none" | |
| new_info["layout_template"] = f"{title_to_chart}_{image_to_chart}" | |
| # 获取并增加计数器 | |
| with lock: | |
| folder_id = counter.value | |
| counter.value += 1 | |
| new_folder_name = f"{folder_id:08d}" | |
| # 创建目标文件夹 | |
| new_folder_path = os.path.join(output_dir, new_folder_name) | |
| os.makedirs(new_folder_path, exist_ok=True) | |
| # 复制文件到新文件夹 | |
| for file in ["chart.svg", "chart.png", "data.json", "info.json"]: | |
| src_file = os.path.join(folder_path, file) | |
| dst_file = os.path.join(new_folder_path, file) | |
| shutil.copy2(src_file, dst_file) | |
| # 保存新的info.json | |
| with open(os.path.join(new_folder_path, "info.json"), 'w') as f: | |
| json.dump(new_info, f, indent=2) | |
| # 新增:记录 chart_variation 和 layout_template 到txt文件 | |
| try: | |
| chart_variation_to_log = new_info.get("chart_variation", "") | |
| layout_template_to_log = new_info.get("layout_template", "") | |
| chart_variation_log_path = os.path.join(output_dir, "chart_variations.txt") | |
| layout_template_log_path = os.path.join(output_dir, "layout_templates.txt") | |
| with open(chart_variation_log_path, 'a', encoding='utf-8') as cv_file: | |
| cv_file.write(f"{chart_variation_to_log}\n") | |
| with open(layout_template_log_path, 'a', encoding='utf-8') as lt_file: | |
| lt_file.write(f"{layout_template_to_log}\n") | |
| except Exception as e: | |
| print(f"警告:无法写入日志文件到 {output_dir}。chart_variation: {chart_variation_to_log}, layout_template: {layout_template_to_log}。错误: {e}") | |
| return { | |
| "original_path": folder_path, | |
| "new_path": new_folder_path, | |
| "overlap_rate": overlap_rate, | |
| "discarded": False | |
| } | |
| def get_max_folder_number(directory): | |
| """获取目录中最大的数字文件夹编号""" | |
| if not os.path.exists(directory): | |
| return -1 | |
| max_num = -1 | |
| for folder_name in os.listdir(directory): | |
| folder_path = os.path.join(directory, folder_name) | |
| if os.path.isdir(folder_path): | |
| try: | |
| num = int(folder_name) | |
| max_num = max(max_num, num) | |
| except ValueError: | |
| # 如果文件夹名不是纯数字,则忽略 | |
| pass | |
| return max_num | |
| def process_folders(): | |
| """处理文件夹并检测SVG文本重叠""" | |
| parser = argparse.ArgumentParser(description="处理SVG文件并检测文本重叠") | |
| parser.add_argument("--input", "-i", nargs='+', required=True, help="输入文件夹路径,可以指定多个") | |
| parser.add_argument("--output", "-o", default="./converted2", help="输出文件夹路径") | |
| parser.add_argument("--processes", "-p", type=int, default=1, help="进程数") | |
| parser.add_argument("--keep-all", action="store_true", help="无条件保留所有文件夹,不做任何丢弃判断") | |
| args = parser.parse_args() | |
| # 创建输出目录 | |
| os.makedirs(args.output, exist_ok=True) | |
| # 收集所有输入文件夹中的子文件夹 | |
| subfolders = [] | |
| for input_dir in args.input: | |
| if not os.path.exists(input_dir): | |
| print(f"警告:输入文件夹 {input_dir} 不存在,已跳过") | |
| continue | |
| for item in os.listdir(input_dir): | |
| item_path = os.path.join(input_dir, item) | |
| if os.path.isdir(item_path): | |
| subfolders.append(item_path) | |
| if not subfolders: | |
| print("错误:未找到任何有效的输入文件夹") | |
| return | |
| print(f"找到 {len(subfolders)} 个待处理文件夹") | |
| # 扫描输出目录获取最大编号 | |
| max_number = get_max_folder_number(args.output) | |
| start_number = max_number + 1 if max_number >= 0 else 0 | |
| print(f"将从编号 {start_number} 开始") | |
| # 初始化计数器和锁 | |
| manager = Manager() | |
| counter = manager.Value('i', start_number) | |
| lock = manager.Lock() | |
| # 准备多进程参数 | |
| process_args = [(folder, args.output, counter, lock, args.keep_all) for folder in subfolders] | |
| # 使用多进程处理 | |
| with Pool(processes=args.processes) as pool: | |
| results = list(tqdm(pool.imap(process_single_folder, process_args), total=len(subfolders))) | |
| # 统计处理结果 | |
| processed = [r for r in results if r is not None] | |
| discarded = [r for r in processed if r["discarded"]] | |
| no_image = [r for r in discarded if r.get("reason") == "no_image_tag"] | |
| text_overlap = [r for r in discarded if r.get("reason") == "text_overlap"] | |
| not_in_mapping = [r for r in discarded if r.get("reason") == "chart_not_in_mapping"] | |
| chart_excluded = [r for r in discarded if r.get("reason") == "chart_type_excluded"] | |
| min_font_size_violations = [r for r in discarded if r.get("reason") == "min_font_size_violation"] | |
| svg_too_large_skips = [r for r in discarded if r.get("reason") == "svg_too_large"] | |
| svg_size_check_error_skips = [r for r in discarded if r.get("reason") == "svg_size_check_error"] | |
| info_json_decode_errors = [r for r in discarded if r.get("reason") == "info_json_decode_error"] | |
| svg_parse_errors = [r for r in discarded if r.get("reason") == "svg_parse_error"] | |
| insufficient_data = [r for r in discarded if r.get("reason") == "insufficient_data"] | |
| data_json_decode_errors = [r for r in discarded if r.get("reason") == "data_json_decode_error"] | |
| print(f"处理完成! 共处理 {len(processed)} 个文件夹,其中保留 {len(processed) - len(discarded)} 个,丢弃 {len(discarded)} 个。") | |
| print(f"丢弃原因: 没有图像标签 {len(no_image)} 个,文本重叠 {len(text_overlap)} 个," | |
| f"chart不在mapping中 {len(not_in_mapping)} 个,chart类型被排除 {len(chart_excluded)} 个," | |
| f"最小字体违规 {len(min_font_size_violations)} 个,SVG文件过大 {len(svg_too_large_skips)} 个,SVG大小检查错误 {len(svg_size_check_error_skips)} 个," | |
| f"info.json解析错误 {len(info_json_decode_errors)} 个,SVG解析错误 {len(svg_parse_errors)} 个," | |
| f"数据量不足 {len(insufficient_data)} 个,data.json解析错误 {len(data_json_decode_errors)} 个。") | |
| def has_rotate_transform(elem, root): | |
| """检查元素或其父元素是否存在rotate变换""" | |
| current = elem | |
| while current is not None: | |
| transform = current.get("transform", "") | |
| if transform and "rotate" in transform: | |
| return True | |
| if current == root: | |
| break | |
| current = current.getparent() | |
| return False | |
| if __name__ == "__main__": | |
| process_folders() |