mtext.py 5.6 KB

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