def draw_filled_paths( self, paths: Iterable[Path], holes: Iterable[Path], properties: Properties, ) -> None: paths = transform_paths(paths, self._scaling_matrix) holes = transform_paths(holes, self._scaling_matrix) self._backend.draw_filled_paths(paths, holes, properties)
def test_one_path_line_to(self): path = Path() path.line_to((1, 0)) result = transform_paths([path], Matrix44()) path0 = result[0] assert path0[0].type == Command.LINE_TO assert path0.start == (0, 0) assert path0.end == (1, 0)
def test_one_path_curve4_to(self): path = Path() path.curve4_to((2, 0), (0, 1), (2, 1)) result = transform_paths([path], Matrix44()) path0 = result[0] assert path0[0].type == Command.CURVE4_TO assert len(path0[0]) == 3 assert path0.start == (0, 0) assert path0.end == (2, 0)
def test_multi_path_objects(self): path = Path() path.line_to((1, 0, 0)) path.move_to((2, 0, 0)) paths = transform_paths([path], Matrix44.translate(0, 1, 0)) assert len(paths) == 1 path2 = paths[0] assert path2.start.isclose((0, 1, 0)) assert len(path2) == 2 assert path2.end.isclose((2, 1, 0)) assert path2.has_sub_paths is True
def test_one_path_multiple_command(self): path = Path() path.line_to((1, 0)) path.curve3_to((2, 0), (2.5, 1)) path.curve4_to((3, 0), (2, 1), (3, 1)) result = transform_paths([path], Matrix44()) path0 = result[0] assert path0[0].type == Command.LINE_TO assert path0[1].type == Command.CURVE3_TO assert path0[2].type == Command.CURVE4_TO assert path0.start == (0, 0) assert path0.end == (3, 0)
def test_two_paths_one_command(self): path_a = Path() path_a.line_to((1, 0)) path_b = Path((2, 0)) path_b.line_to((3, 0)) result = transform_paths([path_a, path_b], Matrix44()) path0 = result[0] assert path0[0].type == Command.LINE_TO assert path0.start == (0, 0) assert path0.end == (1, 0) path1 = result[1] assert path1[0].type == Command.LINE_TO assert path1.start == (2, 0) assert path1.end == (3, 0)
def make_paths_from_str(s: str, font: fonts.FontFace, size: float = 1.0, align: str = 'LEFT', length: float = 0, m: Matrix44 = None) -> List[Path]: """ Convert a single line string `s` into a list of :class:`~ezdxf.path.Path` objects. All paths are returned in a single list. The text `size` is the height of the uppercase letter "X" (cap height). The paths are aligned about the insertion point at (0, 0). BASELINE means the bottom of the letter "X". Args: s: text to convert font: font face definition size: text size (cap height) in drawing units align: alignment as string, default is "LEFT" length: target length for the "ALIGNED" and "FIT" alignments m: transformation :class:`~ezdxf.math.Matrix44` """ if len(s) == 0: return [] font_properties, font_measurements = _get_font_data(font) scaled_size = size / font_measurements.cap_height scaled_fm = font_measurements.scale_from_baseline(scaled_size) paths = _str_to_paths(s, font_properties, scaled_size) bbox = path.bbox(paths, precise=False) halign, valign = const.TEXT_ALIGN_FLAGS[align.upper()] matrix = get_alignment_transformation(scaled_fm, bbox, halign, valign) stretch_x = 1.0 stretch_y = 1.0 if align == 'ALIGNED': stretch_x = length / bbox.size.x stretch_y = stretch_x elif align == 'FIT': stretch_x = length / bbox.size.x if stretch_x != 1.0: matrix *= Matrix44.scale(stretch_x, stretch_y, 1.0) if m is not None: matrix *= m return list(path.transform_paths(paths, matrix))
def make_paths_from_entity(entity: AnyText) -> List[Path]: """ Convert text content from DXF entities TEXT and ATTRIB into a list of :class:`~ezdxf.path.Path` objects. All paths are returned in a single list. The paths are located at the location of the source entity. """ check_entity_type(entity) fonts.load() text = entity.plain_text() paths = make_paths_from_str( text, fonts.get_font_face(entity.font_name()), size=entity.dxf.height, # cap height in drawing units align=entity.get_align(), length=entity.fit_length(), ) m = entity.wcs_transformation_matrix() return path.transform_paths(paths, m)
def test_two_paths_multiple_commands(self): path_a = Path() path_a.line_to((1, 0)) path_a.curve3_to((2, 0), (2.5, 1)) path_a.curve4_to((3, 0), (2, 1), (3, 1)) path_b = path_a.transform(Matrix44.translate(4, 0, 0)) result = transform_paths([path_a, path_b], Matrix44()) path0 = result[0] assert path0[0].type == Command.LINE_TO assert path0[1].type == Command.CURVE3_TO assert path0[2].type == Command.CURVE4_TO assert path0.start == (0, 0) assert path0.end == (3, 0) path1 = result[1] assert path1[0].type == Command.LINE_TO assert path1[1].type == Command.CURVE3_TO assert path1[2].type == Command.CURVE4_TO assert path1.start == (4, 0) assert path1.end == (7, 0)
def make_paths_from_str(s: str, font: fonts.FontFace, size: float = 1.0, align: str = 'LEFT', length: float = 0, m: Matrix44 = None) -> List[Path]: """ Convert a single line string `s` into a list of :class:`~ezdxf.path.Path` objects. All paths are returned in a single list. The text `size` is the height of the uppercase letter "X" (cap height). The paths are aligned about the insertion point at (0, 0). BASELINE means the bottom of the letter "X". Args: s: text to convert font: font face definition as :class:`~ezdxf.tools.fonts.FontFace` object size: text size (cap height) in drawing units align: alignment as string, default is "LEFT" length: target length for the "ALIGNED" and "FIT" alignments m: transformation :class:`~ezdxf.math.Matrix44` """ if len(s) == 0: return [] font_properties, font_measurements = _get_font_data(font) # scale font rendering units to drawing units: render_size = size / font_measurements.cap_height paths = _str_to_paths(s, font_properties, render_size) bbox = path.bbox(paths, flatten=False) # Text is rendered in drawing units, # therefore do alignment in drawing units: draw_units_fm = font_measurements.scale_from_baseline(size) matrix = alignment_transformation(draw_units_fm, bbox, align, length) if m is not None: matrix *= m return list(path.transform_paths(paths, matrix))
# create the target box: msp.add_lwpolyline([(0, 0), (sx, 0), (sx, sy), (0, sy)], close=True, dxfattribs={'color': 1}) # convert text string into path objects: text_as_paths = text2path.make_paths_from_str("Squeeze Me", ff) # fit text paths into a given box size by scaling, does not move the path objects: # uniform=True, keeps the text aspect ratio # uniform=False, scales the text to touch all 4 sides of the box final_paths = path.fit_paths_into_box(text_as_paths, size=(sx, sy, 0), uniform=False) # mirror text along x-axis final_paths = path.transform_paths(final_paths, Matrix44.scale(-1, 1, 1)) # move bottom/left corner to (0, 0) if required: bbox = path.bbox(final_paths) dx, dy, dz = -bbox.extmin final_paths = path.transform_paths(final_paths, Matrix44.translate(dx, dy, dz)) path.render_lwpolylines(msp, final_paths, distance=0.01, dxfattribs={'color': 2}) zoom.extents(msp) doc.saveas(DIR / 'SqeezeMe.dxf')
def test_transformation_is_executed(self): # Real transformation is just tested once, because Matrix44 # transformation is tested in 605: result = transform_paths([Path((1, 2, 3))], Matrix44.translate(1, 1, 1)) assert result[0].start == (2, 3, 4)
def test_start_point_only_paths(self): result = transform_paths([Path((1, 2, 3))], Matrix44()) assert len(result) == 1 assert len(result[0]) == 0 assert result[0].start == (1, 2, 3)
def test_empty_paths(self): result = transform_paths([], Matrix44()) assert len(result) == 0
def make_paths_from_entity(entity: AnyText) -> List[Path]: """ Convert text content from DXF entities TEXT and ATTRIB into a list of :class:`~ezdxf.path.Path` objects. All paths are returned in a single list. The paths are located at the location of the source entity, but don't expect a 100% match compared to CAD applications. """ def get_font_name(): font_name = 'arial.ttf' style_name = entity.dxf.style if entity.doc: try: style = entity.doc.styles.get(style_name) font_name = style.dxf.font except ValueError: pass return font_name def get_transformation(): """ Apply rotation, width factor, translation to the insertion point and if necessary transformation from OCS to WCS. """ # TODO: text generation flags - mirror-x and mirror-y angle = math.radians(entity.dxf.rotation) width_factor = entity.dxf.width if align == 'LEFT': location = p1 elif align in ('ALIGNED', 'FIT'): width_factor = 1.0 # text goes from p1 to p2, no stretching applied location = p1.lerp(p2, factor=0.5) angle = (p2 - p1).angle # override stored angle else: location = p2 m = Matrix44.chain( Matrix44.scale(width_factor, 1, 1), Matrix44.z_rotate(angle), Matrix44.translate(location.x, location.y, location.z), ) ocs = entity.ocs() if ocs.transform: m *= ocs.matrix return m if not entity.dxftype() in ('TEXT', 'ATTRIB'): raise TypeError(f'unsupported entity type: {entity.dxftype()}') fonts.load() text = entity.plain_text() align = entity.get_align() p1 = Vec3(entity.dxf.insert) if entity.dxf.hasattr('align_point'): p2 = Vec3(entity.dxf.align_point) else: p2 = p1 length = 0 if align in ('FIT', 'ALIGNED'): # text is stretch between p1 and p2 length = p1.distance(p2) paths = make_paths_from_str( text, fonts.get_font_face(get_font_name()), size=entity.dxf.height, # cap height in drawing units align=align, length=length, ) m = get_transformation() return path.transform_paths(paths, m)