- Add\Rework UI - Add Split Table and Listing - Add Support Customazeble schems
This commit is contained in:
+70
-10
@@ -7,20 +7,32 @@ import os.path
|
||||
|
||||
import requests
|
||||
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
|
||||
from docx.shared import Parented, Length
|
||||
from docx.shared import Parented, Length, Mm
|
||||
from docx.text.paragraph import Paragraph
|
||||
|
||||
from .caption import Caption, CaptionInfo
|
||||
from .renderable import Renderable
|
||||
from .requires_numbering import RequiresNumbering
|
||||
from ..docx_svg import attach_svg_blip
|
||||
from ..layout_tracker import LayoutState
|
||||
from ..rendered_info import RenderedInfo
|
||||
from ..sub_renderable import SubRenderable
|
||||
from ..util import create_element
|
||||
|
||||
# Leave room under the figure so «Рисунок N — …» stays on the same page.
|
||||
_CAPTION_RESERVE = Mm(12)
|
||||
|
||||
|
||||
class Image(Renderable, RequiresNumbering):
|
||||
def __init__(self, parent: Parented, path: str, caption_info: CaptionInfo | None = None):
|
||||
def __init__(
|
||||
self,
|
||||
parent: Parented,
|
||||
path: str,
|
||||
caption_info: CaptionInfo | None = None,
|
||||
svg_path: str | None = None,
|
||||
*,
|
||||
pixel_scale: float = 1.0,
|
||||
):
|
||||
super().__init__("Рисунок")
|
||||
self._parent = parent
|
||||
self._caption_info = caption_info
|
||||
@@ -31,6 +43,8 @@ class Image(Renderable, RequiresNumbering):
|
||||
self._docx_paragraph.paragraph_format.line_spacing = 1
|
||||
self._docx_paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
|
||||
self._invalid = False
|
||||
self._native_width: Length | None = None
|
||||
self._native_height: Length | None = None
|
||||
|
||||
run = self._docx_paragraph.add_run()
|
||||
|
||||
@@ -47,36 +61,82 @@ class Image(Renderable, RequiresNumbering):
|
||||
except FileNotFoundError:
|
||||
logging.warning(f"Invalid image path: {path}, skipping...")
|
||||
self._invalid = True
|
||||
self._image = None
|
||||
|
||||
# High-res PlantUML/Kroki PNG: keep on-page size as if scale were 1.
|
||||
if not self._invalid and pixel_scale and pixel_scale > 1:
|
||||
self._image.width = Length(int(self._image.width / pixel_scale))
|
||||
self._image.height = Length(int(self._image.height / pixel_scale))
|
||||
|
||||
if not self._invalid:
|
||||
self._native_width = Length(int(self._image.width))
|
||||
self._native_height = Length(int(self._image.height))
|
||||
|
||||
if not self._invalid and svg_path:
|
||||
try:
|
||||
attach_svg_blip(run, self._image, svg_path)
|
||||
except Exception as exc:
|
||||
logging.warning("SVG blip attach failed (%s): %s", svg_path, exc)
|
||||
|
||||
self._number = None
|
||||
self.landscape = bool(caption_info and caption_info.landscape)
|
||||
if caption_info and caption_info.unique_name:
|
||||
self.unique_name = caption_info.unique_name
|
||||
|
||||
# Keep figure + caption together across Word pagination.
|
||||
if not self._invalid and caption_info is not None:
|
||||
self._docx_paragraph.paragraph_format.keep_with_next = True
|
||||
|
||||
def set_number(self, number: str):
|
||||
self._number = number
|
||||
|
||||
def _reset_native_size(self) -> None:
|
||||
if self._image is None or self._native_width is None or self._native_height is None:
|
||||
return
|
||||
self._image.width = Length(int(self._native_width))
|
||||
self._image.height = Length(int(self._native_height))
|
||||
|
||||
def render(self, previous_rendered: RenderedInfo, layout_state: LayoutState)\
|
||||
-> Generator[RenderedInfo | SubRenderable, None, None]:
|
||||
if self._invalid:
|
||||
yield from []
|
||||
return
|
||||
|
||||
# Re-fit from native size each time (Paragraph may have measured us earlier).
|
||||
self._reset_native_size()
|
||||
|
||||
has_caption = self._caption_info is not None
|
||||
caption_reserve = _CAPTION_RESERVE if has_caption else Length(0)
|
||||
max_w = layout_state.max_width
|
||||
max_h = Length(max(0, int(layout_state.max_height) - int(caption_reserve)))
|
||||
|
||||
# limit width
|
||||
if self._image.width > layout_state.max_width:
|
||||
if self._image.width > max_w:
|
||||
height_by_width = self._image.height / self._image.width
|
||||
self._image.width = layout_state.max_width
|
||||
self._image.width = max_w
|
||||
self._image.height = Length(self._image.width * height_by_width)
|
||||
|
||||
# limit height
|
||||
if self._image.height > layout_state.max_height:
|
||||
# limit height (leave room for caption on the same page)
|
||||
if self._image.height > max_h:
|
||||
width_by_height = self._image.width / self._image.height
|
||||
self._image.height = layout_state.max_height
|
||||
self._image.height = max_h
|
||||
self._image.width = Length(self._image.height * width_by_height)
|
||||
|
||||
height = self._image.height
|
||||
|
||||
if layout_state.remaining_page_height < height:
|
||||
height += layout_state.remaining_page_height
|
||||
need = Length(int(height) + int(caption_reserve))
|
||||
remaining = layout_state.remaining_page_height
|
||||
if remaining < need:
|
||||
if self.landscape:
|
||||
# Already on a fresh landscape section — shrink instead of soft page-break
|
||||
# (soft break + section break = empty landscape page before the figure).
|
||||
avail = Length(max(0, int(remaining) - int(caption_reserve)))
|
||||
if avail > 0 and self._image.height > avail:
|
||||
ratio = int(avail) / int(self._image.height)
|
||||
self._image.height = avail
|
||||
self._image.width = Length(int(self._image.width * ratio))
|
||||
height = self._image.height
|
||||
else:
|
||||
height = Length(int(height) + int(remaining))
|
||||
|
||||
yield (rendered_image := RenderedInfo(self._docx_paragraph, Length(height)))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user