Spaces:
Paused
Paused
| from __future__ import annotations | |
| from dataclasses import dataclass, field | |
| from pathlib import Path | |
| from typing import Any | |
| class StyleSpec: | |
| weight: str = "regular" # "regular" | "bold" | |
| style_: str = "normal" # "normal" | "italic" | |
| align: str = "left" # "left" | "center" | "right" | |
| class SizingConfig: | |
| detect: bool = True | |
| cluster_eps_pt: float = 2.0 | |
| # Maps group name → list of labels belonging to that group | |
| cluster_groups: dict[str, list[str]] = field( | |
| default_factory=lambda: { | |
| "body": [ | |
| "Text", | |
| "ListItem", | |
| "Footnote", | |
| "Handwriting", | |
| "TextInlineMath", | |
| ], | |
| "toc": ["TableOfContents"], | |
| "equation": ["Equation"], | |
| "headings": ["SectionHeader"], | |
| "header_footer": ["PageHeader", "PageFooter"], | |
| "caption": ["Caption"], | |
| } | |
| ) | |
| # "document" = cluster across all pages; "page" = cluster per page | |
| cluster_scope_by_group: dict[str, str] = field( | |
| default_factory=lambda: { | |
| "body": "page", | |
| "toc": "document", | |
| "equation": "page", | |
| "headings": "page", | |
| "header_footer": "page", | |
| "caption": "page", | |
| } | |
| ) | |
| cap_height_ratio: float = 0.8 | |
| fallback_size: float = 11.0 | |
| # Used by _estimate_fit_size: avg char width / font_size and line-height / font_size | |
| char_width_ratio: float = 0.55 | |
| leading_ratio: float = 1.25 | |
| # Table cell tweaks: slightly smaller font + inset to avoid border overlap | |
| cell_font_scale: float = 0.88 | |
| cell_bbox_inset_pt: float = 2.0 | |
| class BackgroundConfig: | |
| enabled: bool = True | |
| sample_margin_pt: float = 6.0 | |
| dpi_scale: float = 2.0 | |
| complexity_brightness_spread: float = 72.0 | |
| text_contamination_dark_value: int = 220 | |
| text_contamination_dark_ratio: float = 0.015 | |
| min_sample_pixels: int = 24 | |
| eraser_padding_pt: float = 1.5 | |
| fallback_bg: tuple[int, int, int] = (255, 255, 255) | |
| class TextColorConfig: | |
| enabled: bool = True | |
| center_fraction: float = 0.6 | |
| fallback: tuple[int, int, int] = (0, 0, 0) | |
| class CompressConfig: | |
| subset_fonts: bool = True | |
| deflate: bool = True | |
| pikepdf_image_recompress: bool = False | |
| target_dpi: int = 200 | |
| jpeg_quality: int = 78 | |
| class RenderConfig: | |
| # Typst font configuration | |
| typst_font_paths: list[str] = field(default_factory=list) | |
| # Single name or fallback chain. Typst tries each in order when a glyph | |
| # is missing — useful for mixed-script content (Vietnamese, Greek, etc.). | |
| font_family: str | list[str] = "Helvetica" | |
| # Optional per-label style overrides | |
| styles: dict[str, StyleSpec] = field( | |
| default_factory=lambda: { | |
| "SectionHeader": StyleSpec(weight="bold"), | |
| "PageHeader": StyleSpec(align="center"), | |
| "PageFooter": StyleSpec(align="center"), | |
| "Caption": StyleSpec(style_="italic", align="center"), | |
| "TableOfContents": StyleSpec(), | |
| } | |
| ) | |
| default_style: StyleSpec = field(default_factory=StyleSpec) | |
| cell_style: StyleSpec = field(default_factory=StyleSpec) | |
| sizing: SizingConfig = field(default_factory=SizingConfig) | |
| background: BackgroundConfig = field(default_factory=BackgroundConfig) | |
| text_color: TextColorConfig = field(default_factory=TextColorConfig) | |
| compress: CompressConfig = field(default_factory=CompressConfig) | |
| min_font_size_pt: float = 7.0 | |
| expand_downward: bool = True | |
| max_expand_pt: float = 80.0 | |
| # Remove native text layer in translatable regions (needed for non-scanned PDFs) | |
| redact_native_text: bool = True | |
| pages: list[int] | None = None | |
| typst_binary: str = "typst" | |
| keep_typst_source: bool = False | |
| # Legacy PyMuPDF fallback fields (kept for the fallback renderer) | |
| font_path: str = "" | |
| font_name: str = "Body" | |
| def from_json(cls, path: str | Path) -> "RenderConfig": | |
| import json | |
| data = json.loads(Path(path).read_text(encoding="utf-8")) | |
| cfg = cls() | |
| if "font_family" in data: | |
| cfg.font_family = data["font_family"] | |
| if "typst_font_paths" in data: | |
| cfg.typst_font_paths = data["typst_font_paths"] | |
| if "typst_binary" in data: | |
| cfg.typst_binary = data["typst_binary"] | |
| if "font_path" in data: | |
| cfg.font_path = data["font_path"] | |
| if "min_font_size_pt" in data: | |
| cfg.min_font_size_pt = float(data["min_font_size_pt"]) | |
| if "pages" in data: | |
| cfg.pages = data["pages"] | |
| _load_nested(cfg.sizing, data.get("sizing", {})) | |
| _load_nested(cfg.background, data.get("background", {})) | |
| _load_nested(cfg.text_color, data.get("text_color", {})) | |
| _load_nested(cfg.compress, data.get("compress", {})) | |
| return cfg | |
| def _load_nested(obj: Any, d: dict) -> None: | |
| for k, v in d.items(): | |
| if hasattr(obj, k): | |
| setattr(obj, k, v) | |