123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271 |
- # Created: 24.05.2015
- # Copyright (c) 2015-2018, Manfred Moitzi
- # License: MIT License
- from typing import TYPE_CHECKING, List, Union, Tuple
- from contextlib import contextmanager
- import math
- from ezdxf.math.vector import Vector
- from ezdxf.lldxf.attributes import DXFAttr, DXFAttributes, DefSubclass, XType
- from ezdxf.lldxf.tags import DXFTag
- from ezdxf.lldxf.extendedtags import ExtendedTags
- from ezdxf.lldxf import const
- from ezdxf.lldxf.const import DXFValueError
- from ezdxf import rgb2int
- from .graphics import none_subclass, entity_subclass, ModernGraphicEntity
- if TYPE_CHECKING:
- from ezdxf.eztypes import Vertex
- _MTEXT_TPL = """0
- MTEXT
- 5
- 0
- 330
- 0
- 100
- AcDbEntity
- 8
- 0
- 100
- AcDbMText
- 40
- 1.0
- 71
- 1
- 73
- 1
- 1
- """
- mtext_subclass = DefSubclass('AcDbMText', {
- 'insert': DXFAttr(10, xtype=XType.point3d),
- 'char_height': DXFAttr(40), # nominal (initial) text height
- 'width': DXFAttr(41), # reference column width
- 'attachment_point': DXFAttr(71),
- # 1 = Top left; 2 = Top center; 3 = Top right
- # 4 = Middle left; 5 = Middle center; 6 = Middle right
- # 7 = Bottom left; 8 = Bottom center; 9 = Bottom right
- 'flow_direction': DXFAttr(72),
- # 1 = Left to right
- # 3 = Top to bottom
- # 5 = By style (the flow direction is inherited from the associated text style)
- 'style': DXFAttr(7, default='STANDARD'), # text style name
- 'extrusion': DXFAttr(210, xtype=XType.point3d, default=(0.0, 0.0, 1.0)),
- 'text_direction': DXFAttr(11, xtype=XType.point3d), # x-axis direction vector (in WCS)
- # If *rotation* and *text_direction* are present, *text_direction* wins
- 'rect_width': DXFAttr(42), # Horizontal width of the characters that make up the mtext entity.
- # This value will always be equal to or less than the value of *width*, (read-only, ignored if supplied)
- 'rect_height': DXFAttr(43), # vertical height of the mtext entity (read-only, ignored if supplied)
- 'rotation': DXFAttr(50, default=0.0), # in degrees (circle=360 deg) - error in DXF reference, which says radians
- 'line_spacing_style': DXFAttr(73), # line spacing style (optional):
- # 1 = At least (taller characters will override)
- # 2 = Exact (taller characters will not override)
- 'line_spacing_factor': DXFAttr(44), # line spacing factor (optional):
- # Percentage of default (3-on-5) line spacing to be applied. Valid values
- # range from 0.25 to 4.00
- 'box_fill_scale': DXFAttr(45, dxfversion='AC1021'),
- # Determines how much border there is around the text.
- # (45) + (90) + (63) all three required, if one of them is used
- 'bg_fill': DXFAttr(90, dxfversion='AC1021'), # background fill type:
- # 0=off;
- # 1=color -> (63) < (421) or (431);
- # 2=drawing window color
- # 3=use background color
- 'bg_fill_color': DXFAttr(63, dxfversion='AC1021'), # background fill color as ACI, required even true color is used
- 'bg_fill_true_color': DXFAttr(421, dxfversion='AC1021'), # background fill color as true color value, (63) also required but ignored
- 'bg_fill_color_name': DXFAttr(431, dxfversion='AC1021'), # background fill color as color name ???, (63) also required but ignored
- 'bg_fill_transparency': DXFAttr(441, dxfversion='AC1021'), # background fill color transparency - not used by AutoCAD/BricsCAD
- })
- class MText(ModernGraphicEntity): # MTEXT will be extended in DXF version AC1021 (ACAD 2007)
- __slots__ = ()
- TEMPLATE = ExtendedTags.from_text(_MTEXT_TPL)
- DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, mtext_subclass)
- def get_text(self) -> str:
- tags = self.tags.get_subclass('AcDbMText')
- tail = ""
- parts = []
- for tag in tags:
- if tag.code == 1:
- tail = tag.value
- if tag.code == 3:
- parts.append(tag.value)
- parts.append(tail)
- return "".join(parts)
- def set_text(self, text: str) -> 'MText':
- tags = self.tags.get_subclass('AcDbMText')
- tags.remove_tags(codes=(1, 3))
- str_chunks = split_string_in_chunks(text, size=250)
- if len(str_chunks) == 0:
- str_chunks.append("")
- while len(str_chunks) > 1:
- tags.append(DXFTag(3, str_chunks.pop(0)))
- tags.append(DXFTag(1, str_chunks[0]))
- return self
- def get_rotation(self) -> float:
- try:
- vector = self.dxf.text_direction
- except DXFValueError:
- rotation = self.get_dxf_attrib('rotation', 0.0)
- else:
- radians = math.atan2(vector[1], vector[0]) # ignores z-axis
- rotation = math.degrees(radians)
- return rotation
- def set_rotation(self, angle: float) -> 'MText':
- del self.dxf.text_direction # *text_direction* has higher priority than *rotation*, therefore delete it
- self.dxf.rotation = angle
- return self
- def set_location(self, insert: 'Vertex', rotation: float = None, attachment_point: int = None) -> 'MText':
- self.dxf.insert = Vector(insert)
- if rotation is not None:
- self.set_rotation(rotation)
- if attachment_point is not None:
- self.dxf.attachment_point = attachment_point
- return self
- def set_bg_color(self, color: Union[int, str, Tuple[int, int, int], None], scale: float = 1.5):
- self.dxf.box_fill_scale = scale
- if color is None:
- self.del_dxf_attrib('bg_fill')
- self.del_dxf_attrib('box_fill_scale')
- self.del_dxf_attrib('bg_fill_color')
- self.del_dxf_attrib('bg_fill_true_color')
- self.del_dxf_attrib('bg_fill_color_name')
- elif color == 'canvas': # special case for use background color
- self.dxf.bg_fill = const.MTEXT_BG_CANVAS_COLOR
- self.dxf.bg_fill_color = 0 # required but ignored
- else:
- self.dxf.bg_fill = const.MTEXT_BG_COLOR
- if isinstance(color, int):
- self.dxf.bg_fill_color = color
- elif isinstance(color, str):
- self.dxf.bg_fill_color = 0 # required but ignored
- self.dxf.bg_fill_color_name = color
- elif isinstance(color, tuple):
- self.dxf.bg_fill_color = 0 # required but ignored
- self.dxf.bg_fill_true_color = rgb2int(color)
- return self # fluent interface
- @contextmanager
- def edit_data(self) -> 'MTextData':
- buffer = MTextData(self.get_text())
- yield buffer
- self.set_text(buffer.text)
- buffer = edit_data # alias
- ##################################################
- # MTEXT inline codes
- # \L Start underline
- # \l Stop underline
- # \O Start overstrike
- # \o Stop overstrike
- # \K Start strike-through
- # \k Stop strike-through
- # \P New paragraph (new line)
- # \pxi Control codes for bullets, numbered paragraphs and columns
- # \X Paragraph wrap on the dimension line (only in dimensions)
- # \Q Slanting (obliquing) text by angle - e.g. \Q30;
- # \H Text height - e.g. \H3x;
- # \W Text width - e.g. \W0.8x;
- # \F Font selection
- #
- # e.g. \Fgdt;o - GDT-tolerance
- # e.g. \Fkroeger|b0|i0|c238|p10 - font Kroeger, non-bold, non-italic, codepage 238, pitch 10
- #
- # \S Stacking, fractions
- #
- # e.g. \SA^B:
- # A
- # B
- # e.g. \SX/Y:
- # X
- # -
- # Y
- # e.g. \S1#4:
- # 1/4
- #
- # \A Alignment
- #
- # \A0; = bottom
- # \A1; = center
- # \A2; = top
- #
- # \C Color change
- #
- # \C1; = red
- # \C2; = yellow
- # \C3; = green
- # \C4; = cyan
- # \C5; = blue
- # \C6; = magenta
- # \C7; = white
- #
- # \T Tracking, char.spacing - e.g. \T2;
- # \~ Non-wrapping space, hard space
- # {} Braces - define the text area influenced by the code
- # \ Escape character - e.g. \\ = "\", \{ = "{"
- #
- # Codes and braces can be nested up to 8 levels deep
- class MTextData:
- UNDERLINE_START = '\\L;'
- UNDERLINE_STOP = '\\l;'
- UNDERLINE = UNDERLINE_START + '%s' + UNDERLINE_STOP
- OVERSTRIKE_START = '\\O;'
- OVERSTRIKE_STOP = '\\o;'
- OVERSTRIKE = OVERSTRIKE_START + '%s' + OVERSTRIKE_STOP
- STRIKE_START = '\\K;'
- STRIKE_STOP = '\\k;'
- STRIKE = STRIKE_START + '%s' + STRIKE_STOP
- NEW_LINE = '\\P;'
- GROUP_START = '{'
- GROUP_END = '}'
- GROUP = GROUP_START + '%s' + GROUP_END
- NBSP = '\\~' # none breaking space
- def __init__(self, text: str):
- self.text = text
- def __iadd__(self, text: str) -> 'MTextData':
- self.text += text
- return self
- append = __iadd__
- def set_font(self, name: str, bold: bool = False, italic: bool = False, codepage: int = 1252,
- pitch: int = 0) -> None:
- bold_flag = 1 if bold else 0
- italic_flag = 1 if italic else 0
- s = "\\F{}|b{}|i{}|c{}|p{};".format(name, bold_flag, italic_flag, codepage, pitch)
- self.append(s)
- def set_color(self, color_name: str) -> None:
- self.append("\\C%d" % const.MTEXT_COLOR_INDEX[color_name.lower()])
- def split_string_in_chunks(s: str, size: int = 250) -> List[str]:
- chunks = []
- pos = 0
- while True:
- chunk = s[pos:pos + size]
- chunk_len = len(chunk)
- if chunk_len:
- chunks.append(chunk)
- if chunk_len < size:
- return chunks
- pos += size
- else:
- return chunks
|