Files
stepanalyser/.venv/lib/python3.12/site-packages/ezdxf/entities/textstyle.py
Christian Anetzberger a197de9456 initial
2026-01-22 20:23:51 +01:00

249 lines
8.6 KiB
Python

# Copyright (c) 2019-2022, Manfred Moitzi
# License: MIT License
from __future__ import annotations
from typing import TYPE_CHECKING, Optional
import logging
from ezdxf.lldxf import validator, const
from ezdxf.lldxf.attributes import (
DXFAttr,
DXFAttributes,
DefSubclass,
RETURN_DEFAULT,
group_code_mapping,
)
from ezdxf.lldxf.const import DXF12, SUBCLASS_MARKER
from ezdxf.entities.dxfentity import base_class, SubclassProcessor, DXFEntity
from ezdxf.entities.layer import acdb_symbol_table_record
from .factory import register_entity
if TYPE_CHECKING:
from ezdxf.entities import DXFNamespace
from ezdxf.lldxf.tagwriter import AbstractTagWriter
from ezdxf.fonts import fonts
__all__ = ["Textstyle"]
logger = logging.getLogger("ezdxf")
acdb_style = DefSubclass(
"AcDbTextStyleTableRecord",
{
"name": DXFAttr(
2,
default="Standard",
validator=validator.is_valid_table_name,
),
# Flags: Standard flag values (bit-coded values):
# 1 = If set, this entry describes a shape
# 4 = Vertical text
# 16 = If set, table entry is externally dependent on a xref
# 32 = If both this bit and bit 16 are set, the externally dependent xref ...
# 64 = If set, the table entry was referenced by at least one entity in ...
# Vertical text works only for SHX fonts in AutoCAD and BricsCAD
"flags": DXFAttr(70, default=0),
# Fixed height, 0 if not fixed
"height": DXFAttr(
40,
default=0,
validator=validator.is_greater_or_equal_zero,
fixer=RETURN_DEFAULT,
),
# Width factor: a.k.a. "Stretch"
"width": DXFAttr(
41,
default=1,
validator=validator.is_greater_zero,
fixer=RETURN_DEFAULT,
),
# Oblique angle in degree, 0 = vertical
"oblique": DXFAttr(50, default=0),
# Generation flags:
# 2 = backward
# 4 = mirrored in Y
"generation_flags": DXFAttr(71, default=0),
# Last height used:
"last_height": DXFAttr(42, default=2.5),
# Primary font file name:
# ATTENTION: The font file name can be an empty string and the font family
# may be stored in XDATA! See also posts at the (unrelated) issue #380.
"font": DXFAttr(3, default=const.DEFAULT_TEXT_FONT),
# Big font name, blank if none
"bigfont": DXFAttr(4, default=""),
},
)
acdb_style_group_codes = group_code_mapping(acdb_style)
# XDATA: This is not a reliable source for font data!
# 1001 <ctrl> ACAD
# 1000 <str> Arial ; font-family sometimes an empty string!
# 1071 <int> 34 ; flags
# ----
# "Arial" "normal" flags = 34 = 0b00:00000000:00000000:00100010
# "Arial" "italic" flags = 16777250 = 0b01:00000000:00000000:00100010
# "Arial" "bold" flags = 33554466 = 0b10:00000000:00000000:00100010
# "Arial" "bold+italic" flags = 50331682 = 0b11:00000000:00000000:00100010
@register_entity
class Textstyle(DXFEntity):
"""DXF STYLE entity"""
DXFTYPE = "STYLE"
DXFATTRIBS = DXFAttributes(base_class, acdb_symbol_table_record, acdb_style)
ITALIC = 0b01000000000000000000000000
BOLD = 0b10000000000000000000000000
def load_dxf_attribs(
self, processor: Optional[SubclassProcessor] = None
) -> DXFNamespace:
dxf = super().load_dxf_attribs(processor)
if processor:
processor.simple_dxfattribs_loader(dxf, acdb_style_group_codes) # type: ignore
return dxf
def export_entity(self, tagwriter: AbstractTagWriter) -> None:
super().export_entity(tagwriter)
if tagwriter.dxfversion > DXF12:
tagwriter.write_tag2(SUBCLASS_MARKER, acdb_symbol_table_record.name)
tagwriter.write_tag2(SUBCLASS_MARKER, acdb_style.name)
self.dxf.export_dxf_attribs(
tagwriter,
[
"name",
"flags",
"height",
"width",
"oblique",
"generation_flags",
"last_height",
"font",
"bigfont",
],
)
@property
def has_extended_font_data(self) -> bool:
"""Returns ``True`` if extended font data is present."""
return self.has_xdata("ACAD")
def get_extended_font_data(self) -> tuple[str, bool, bool]:
"""Returns extended font data as tuple (font-family, italic-flag,
bold-flag).
The extended font data is optional and not reliable! Returns
("", ``False``, ``False``) if extended font data is not present.
"""
family = ""
italic = False
bold = False
try:
xdata = self.get_xdata("ACAD")
except const.DXFValueError:
pass
else:
if len(xdata) > 1:
group_code, value = xdata[0]
if group_code == 1000:
family = value
group_code, value = xdata[1]
if group_code == 1071:
italic = bool(self.ITALIC & value)
bold = bool(self.BOLD & value)
return family, italic, bold
def set_extended_font_data(
self, family: str = "", *, italic=False, bold=False
) -> None:
"""Set extended font data, the font-family name `family` is not
validated by `ezdxf`. Overwrites existing data.
"""
if self.has_xdata("ACAD"):
self.discard_xdata("ACAD")
flags = 34 # unknown default flags
if italic:
flags += self.ITALIC
if bold:
flags += self.BOLD
self.set_xdata("ACAD", [(1000, family), (1071, flags)])
def discard_extended_font_data(self):
"""Discard extended font data."""
self.discard_xdata("ACAD")
@property
def is_backward(self) -> bool:
"""Get/set text generation flag BACKWARDS, for mirrored text along the
x-axis.
"""
return self.get_flag_state(const.BACKWARD, "generation_flags")
@is_backward.setter
def is_backward(self, state) -> None:
self.set_flag_state(const.BACKWARD, state, "generation_flags")
@property
def is_upside_down(self) -> bool:
"""Get/set text generation flag UPSIDE_DOWN, for mirrored text along
the y-axis.
"""
return self.get_flag_state(const.UPSIDE_DOWN, "generation_flags")
@is_upside_down.setter
def is_upside_down(self, state) -> None:
self.set_flag_state(const.UPSIDE_DOWN, state, "generation_flags")
@property
def is_vertical_stacked(self) -> bool:
"""Get/set style flag VERTICAL_STACKED, for vertical stacked text."""
return self.get_flag_state(const.VERTICAL_STACKED, "flags")
@is_vertical_stacked.setter
def is_vertical_stacked(self, state) -> None:
self.set_flag_state(const.VERTICAL_STACKED, state, "flags")
@property
def is_shape_file(self) -> bool:
"""``True`` if entry describes a shape."""
return self.dxf.name == "" and bool(self.dxf.flags & 1)
def make_font(
self,
cap_height: Optional[float] = None,
width_factor: Optional[float] = None,
) -> fonts.AbstractFont:
"""Returns a font abstraction :class:`~ezdxf.tools.fonts.AbstractFont`
for this text style. Returns a font for a cap height of 1, if the
text style has auto height (:attr:`Textstyle.dxf.height` is 0) and
the given `cap_height` is ``None`` or 0.
Uses the :attr:`Textstyle.dxf.width` attribute if the given `width_factor`
is ``None`` or 0, the default value is 1.
The attribute :attr:`Textstyle.dxf.big_font` is ignored.
"""
from ezdxf.fonts import fonts
ttf = ""
if self.has_extended_font_data:
family, italic, bold = self.get_extended_font_data()
if family:
text_style = "Italic" if italic else "Regular"
text_weight = 700 if bold else 400
font_face = fonts.FontFace(
family=family, style=text_style, weight=text_weight
)
ttf = fonts.find_font_file_name(font_face)
else:
ttf = self.dxf.get("font", const.DEFAULT_TTF)
if ttf == "":
ttf = const.DEFAULT_TTF
if cap_height is None or cap_height == 0.0:
cap_height = self.dxf.height
if cap_height == 0.0:
cap_height = 1.0
if width_factor is None or width_factor == 0.0:
width_factor = self.dxf.width
return fonts.make_font(ttf, cap_height, width_factor) # type: ignore