# Copyright (c) 2010-2022, Manfred Moitzi # License: MIT License from __future__ import annotations from typing import TYPE_CHECKING, Any import math import enum from ezdxf.enums import ( MTextEntityAlignment, MAP_MTEXT_ALIGN_TO_FLAGS, ) from ezdxf.lldxf import const from ezdxf.math import UVec, Vec3 from .mixins import SubscriptAttributes if TYPE_CHECKING: from ezdxf.eztypes import GenericLayoutType TOP_ALIGN = { MTextEntityAlignment.TOP_LEFT, MTextEntityAlignment.TOP_RIGHT, MTextEntityAlignment.TOP_CENTER, } MIDDLE_ALIGN = { MTextEntityAlignment.MIDDLE_LEFT, MTextEntityAlignment.MIDDLE_CENTER, MTextEntityAlignment.MIDDLE_RIGHT, } class Mirror(enum.IntEnum): NONE = 0 MIRROR_X = 2 MIRROR_Y = 4 class MTextSurrogate(SubscriptAttributes): """MTEXT surrogate for DXF R12 build up by TEXT Entities. This add-on was added to simplify the transition from :mod:`dxfwrite` to :mod:`ezdxf`. The rich-text formatting capabilities for the regular MTEXT entity are not supported, if these features are required use the regular MTEXT entity and the :class:`~ezdxf.addons.MTextExplode` add-on to explode the MTEXT entity into DXF primitives. .. important:: The align-point is always the insert-point, there is no need for a second align-point because the horizontal alignments FIT, ALIGN, BASELINE_MIDDLE are not supported. Args: text: content as string insert: insert location in drawing units line_spacing: line spacing in percent of height, 1.5 = 150% = 1+1/2 lines align: text alignment as :class:`~ezdxf.enums.MTextEntityAlignment` enum char_height: text height in drawing units style: :class:`~ezdxf.entities.Textstyle` name as string oblique: oblique angle in degrees, where 0 is vertical rotation: text rotation angle in degrees width_factor: text width factor as float mirror: :attr:`MTextSurrogate.MIRROR_X` to mirror the text horizontal or :attr:`MTextSurrogate.MIRROR_Y` to mirror the text vertical layer: layer name as string color: :ref:`ACI` """ MIRROR_NONE = Mirror.NONE MIRROR_X = Mirror.MIRROR_X MIRROR_Y = Mirror.MIRROR_Y def __init__( self, text: str, insert: UVec, line_spacing: float = 1.5, align=MTextEntityAlignment.TOP_LEFT, char_height: float = 1.0, style="STANDARD", oblique: float = 0.0, rotation: float = 0.0, width_factor: float = 1.0, mirror=Mirror.NONE, layer="0", color: int = const.BYLAYER, ): self.content: list[str] = text.split("\n") self.insert = Vec3(insert) self.line_spacing = float(line_spacing) assert isinstance(align, MTextEntityAlignment) self.align = align self.char_height = float(char_height) self.style = str(style) self.oblique = float(oblique) # in degree self.rotation = float(rotation) # in degree self.width_factor = float(width_factor) self.mirror = int(mirror) # renamed to text_generation_flag in ezdxf self.layer = str(layer) self.color = int(color) @property def line_height(self) -> float: """Absolute line spacing in drawing units.""" return self.char_height * self.line_spacing def render(self, layout: GenericLayoutType) -> None: """Render the multi-line content as separated TEXT entities into the given `layout` instance. """ text_lines = self.content if len(text_lines) > 1: if self.mirror & const.MIRROR_Y: text_lines.reverse() for line_number, text in enumerate(text_lines): align_point = self._get_align_point(line_number) layout.add_text( text, dxfattribs=self._dxfattribs(align_point), ) elif len(text_lines) == 1: layout.add_text( text_lines[0], dxfattribs=self._dxfattribs(self.insert), ) def _get_align_point(self, line_number: int) -> Vec3: """Calculate the align-point depending on the line number.""" x = self.insert.x y = self.insert.y try: z = self.insert.z except IndexError: z = 0.0 # rotation not respected if self.align in TOP_ALIGN: y -= line_number * self.line_height elif self.align in MIDDLE_ALIGN: y0 = line_number * self.line_height full_height = (len(self.content) - 1) * self.line_height y += (full_height / 2) - y0 else: # BOTTOM ALIGN y += (len(self.content) - 1 - line_number) * self.line_height return self._rotate(Vec3(x, y, z)) def _rotate(self, alignpoint: Vec3) -> Vec3: """Rotate `alignpoint` around insert-point about rotation degrees.""" dx = alignpoint.x - self.insert.x dy = alignpoint.y - self.insert.y beta = math.radians(self.rotation) x = self.insert.x + dx * math.cos(beta) - dy * math.sin(beta) y = self.insert.y + dy * math.cos(beta) + dx * math.sin(beta) return Vec3(round(x, 6), round(y, 6), alignpoint.z) def _dxfattribs(self, align_point: Vec3) -> dict[str, Any]: """Build keyword arguments for TEXT entity creation.""" halign, valign = MAP_MTEXT_ALIGN_TO_FLAGS[self.align] return { "insert": align_point, "align_point": align_point, "layer": self.layer, "color": self.color, "style": self.style, "height": self.char_height, "width": self.width_factor, "text_generation_flag": self.mirror, "rotation": self.rotation, "oblique": self.oblique, "halign": halign, "valign": valign, }