1# Purpose: The MText entity is a composite entity, consisting of basic TEXT entities. 2# Created: 09.03.2010, adapted 2018 for ezdxf 3# Copyright (c) 2010-2018, Manfred Moitzi 4# License: MIT License 5""" 6MText -- MultiLine-Text-Entity, created by simple TEXT-Entities. 7 8MTEXT was introduced in R13, so this is a replacement with multiple simple 9TEXT entities. Supports valign (TOP, MIDDLE, BOTTOM), halign (LEFT, CENTER, 10RIGHT), rotation for an arbitrary (!) angle and mirror. 11 12""" 13from typing import TYPE_CHECKING 14import math 15from .mixins import SubscriptAttributes 16import ezdxf 17from ezdxf.lldxf import const 18 19if TYPE_CHECKING: 20 from ezdxf.eztypes import Vertex, GenericLayoutType 21 22 23class MText(SubscriptAttributes): 24 """ 25 MultiLine-Text buildup with simple Text-Entities. 26 27 28 Caution: align point is always the insert point, I don't need a second 29 alignpoint because horizontal alignment FIT, ALIGN, BASELINE_MIDDLE is not 30 supported. 31 32 linespacing -- linespacing in percent of height, 1.5 = 150% = 1+1/2 lines 33 34 supported align values: 35 'BOTTOM_LEFT', 'BOTTOM_CENTER', 'BOTTOM_RIGHT' 36 'MIDDLE_LEFT', 'MIDDLE_CENTER', 'MIDDLE_RIGHT' 37 'TOP_LEFT', 'TOP_CENTER', 'TOP_RIGHT' 38 39 """ 40 MIRROR_X = const.MIRROR_X 41 MIRROR_Y = const.MIRROR_Y 42 TOP = const.TOP 43 MIDDLE = const.MIDDLE 44 BOTTOM = const.BOTTOM 45 LEFT = const.LEFT 46 CENTER = const.CENTER 47 RIGHT = const.RIGHT 48 VALID_ALIGN = frozenset([ 49 'BOTTOM_LEFT', 50 'BOTTOM_CENTER', 51 'BOTTOM_RIGHT', 52 'MIDDLE_LEFT', 53 'MIDDLE_CENTER', 54 'MIDDLE_RIGHT', 55 'TOP_LEFT', 56 'TOP_CENTER', 57 'TOP_RIGHT', 58 ]) 59 60 def __init__(self, text: str, insert: 'Vertex', linespacing: float = 1.5, **kwargs): 61 self.textlines = text.split('\n') 62 self.insert = insert 63 self.linespacing = linespacing 64 if 'align' in kwargs: 65 self.align = kwargs.get('align', 'TOP_LEFT').upper() 66 else: # support for compatibility: valign, halign 67 halign = kwargs.get('halign', 0) 68 valign = kwargs.get('valign', 3) 69 self.align = const.TEXT_ALIGNMENT_BY_FLAGS.get((halign, valign), 'TOP_LEFT') 70 71 if self.align not in MText.VALID_ALIGN: 72 raise ezdxf.DXFValueError('Invalid align parameter: {}'.format(self.align)) 73 74 self.height = kwargs.get('height', 1.0) 75 self.style = kwargs.get('style', 'STANDARD') 76 self.oblique = kwargs.get('oblique', 0.0) # in degree 77 self.rotation = kwargs.get('rotation', 0.0) # in degree 78 self.xscale = kwargs.get('xscale', 1.0) 79 self.mirror = kwargs.get('mirror', 0) # renamed to text_generation_flag in ezdxf 80 self.layer = kwargs.get('layer', '0') 81 self.color = kwargs.get('color', const.BYLAYER) 82 83 @property 84 def lineheight(self) -> float: 85 """ Absolute linespacing in drawing units. 86 """ 87 return self.height * self.linespacing 88 89 def render(self, layout: 'GenericLayoutType') -> None: 90 """ Create the DXF-TEXT entities. 91 """ 92 textlines = self.textlines 93 if len(textlines) > 1: 94 if self.mirror & const.MIRROR_Y: 95 textlines.reverse() 96 for linenum, text in enumerate(textlines): 97 alignpoint = self._get_align_point(linenum) 98 layout.add_text( 99 text, 100 dxfattribs=self._dxfattribs(alignpoint), 101 ) 102 elif len(textlines) == 1: 103 layout.add_text( 104 textlines[0], 105 dxfattribs=self._dxfattribs(self.insert), 106 ) 107 108 def _get_align_point(self, linenum: int) -> 'Vertex': 109 """ Calculate the align point depending on the line number. 110 """ 111 x = self.insert[0] 112 y = self.insert[1] 113 try: 114 z = self.insert[2] 115 except IndexError: 116 z = 0. 117 # rotation not respected 118 119 if self.align.startswith('TOP'): 120 y -= linenum * self.lineheight 121 elif self.align.startswith('MIDDLE'): 122 y0 = linenum * self.lineheight 123 fullheight = (len(self.textlines) - 1) * self.lineheight 124 y += (fullheight / 2) - y0 125 else: # BOTTOM 126 y += (len(self.textlines) - 1 - linenum) * self.lineheight 127 return self._rotate((x, y, z)) # consider rotation 128 129 def _rotate(self, alignpoint: 'Vertex') -> 'Vertex': 130 """ 131 Rotate alignpoint around insert point about rotation degrees. 132 """ 133 dx = alignpoint[0] - self.insert[0] 134 dy = alignpoint[1] - self.insert[1] 135 beta = math.radians(self.rotation) 136 x = self.insert[0] + dx * math.cos(beta) - dy * math.sin(beta) 137 y = self.insert[1] + dy * math.cos(beta) + dx * math.sin(beta) 138 return round(x, 6), round(y, 6), alignpoint[2] 139 140 def _dxfattribs(self, alignpoint: 'Vertex') -> dict: 141 """ 142 Build keyword arguments for TEXT entity creation. 143 """ 144 halign, valign = const.TEXT_ALIGN_FLAGS.get(self.align) 145 return { 146 'insert': alignpoint, 147 'align_point': alignpoint, 148 'layer': self.layer, 149 'color': self.color, 150 'style': self.style, 151 'height': self.height, 152 'width': self.xscale, 153 'text_generation_flag': self.mirror, 154 'rotation': self.rotation, 155 'oblique': self.oblique, 156 'halign': halign, 157 'valign': valign, 158 } 159