def test_scaled_height_and_length_for_aligned_text(self, size, ff): length = 3 paths = text2path.make_paths_from_str("XXX", font=ff, size=size, align="LEFT") default = path.bbox(paths) paths = text2path.make_paths_from_str( "XXX", font=ff, size=size, align="ALIGNED", length=length) bbox = path.bbox(paths) scale = bbox.size.x / default.size.x assert bbox.size.x == pytest.approx(length), "expect exact length" assert bbox.size.y == pytest.approx(size * scale), \ "text height should be scaled"
def test_gear(): # basic gear tool is tested here: test_702_render_forms.py gear = shapes.gear(16, 0.1, 0.3, 0.2, 1.0) # first vertex is on th x-axis! extends = bbox([gear]) assert extends.extmin.isclose((-0.999, -0.999), abs_tol=1e-3) assert extends.extmax.isclose((0.999, 0.999), abs_tol=1e-3) assert gear.is_closed is True
def test_length_for_fit_alignment(self, size, ff): length = 3 paths = text2path.make_paths_from_str( "XXX", font=ff, size=size, align="FIT", length=length) bbox = path.bbox(paths) assert bbox.size.x == pytest.approx(length), "expect exact length" assert bbox.size.y == pytest.approx(size), \ "text height should be unscaled"
def test_star(): # basic star tool is tested here: test_702_render_forms.py star4 = shapes.star(4, r1=1, r2=0.5) # first vertex is on th x-axis! extends = bbox([star4]) assert extends.extmin.isclose((-1, -1)) assert extends.extmax.isclose((1, 1)) assert star4.is_closed is True
def test_ngon(): # basic ngon tool is tested here: test_702_render_forms.py square = shapes.ngon(4, length=1.0) # first vertex is on th x-axis => square is rotated about 45 deg: extends = bbox([square]) d = math.sin(math.pi / 4) assert extends.extmin.isclose((-d, -d)) assert extends.extmax.isclose((d, d)) assert square.is_closed is True
def test_total_length_for_fit_alignment(self, ff): length = 3 hatches = text2path.make_hatches_from_str( "XXX", font=ff, align="FIT", length=length) paths = [] for hatch in hatches: paths.extend(path.from_hatch(hatch)) bbox = path.bbox(paths) assert bbox.size.x == pytest.approx(length), "expect exact length" assert bbox.size.y == pytest.approx(1.0), \ "text height should be unscaled"
def test_wedge(): wedge = shapes.wedge(0, math.pi / 2) assert wedge.has_curves assert wedge.has_lines assert wedge.is_closed is True assert len(wedge) == 3, "expected 2 lines and 1 cubic Bèzier segment" assert wedge.start.isclose((0, 0, 0)), "has to start at the center" assert wedge.end.isclose((0, 0, 0)), "has to end at the center" extends = bbox([wedge]) assert extends.extmin.isclose((0.0, 0.0)) assert extends.extmax.isclose((1.0, 1.0))
def make_path_from_str( s: str, font: fonts.FontFace, size: float = 1.0, align=TextEntityAlignment.LEFT, length: float = 0, m: Matrix44 = None, ) -> Path: """Convert a single line string `s` into a :term:`Multi-Path` object. 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 :class:`ezdxf.enums.TextEntityAlignment`, default is :attr:`LEFT` length: target length for the :attr:`ALIGNED` and :attr:`FIT` alignments m: transformation :class:`~ezdxf.math.Matrix44` .. versionadded:: 0.17 .. version changed: 0.17.2 Enum :class:`ezdxf.enums.TextEntityAlignment` replaces string values. """ if len(s) == 0: return Path() font_properties, font_measurements = _get_font_data(font) # scale font rendering units to drawing units: render_size = size / font_measurements.cap_height p = _str_to_path(s, font_properties, render_size) bbox = path.bbox([p], flatten=0) # 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 p.transform(matrix)
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_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))
def test_uniform_shrink_paths(self, spath): result = fit_paths_into_box([spath], (1.5, 1.5, 1.5)) box = bbox(result) assert box.size.isclose((0.5, 1, 1.5))
def test_uniform_stretch_paths_limited_by_x(self, spath): result = fit_paths_into_box([spath], (1.2, 6, 6)) box = bbox(result) # stretch factor: 1.2 assert box.size.isclose((1.2, 2.4, 3.6))
def test_uniform_stretch_paths_limited_by_y(self, spath): result = fit_paths_into_box([spath], (6, 3, 6)) box = bbox(result) # stretch factor: 1.5 assert box.size == (1.5, 3, 4.5)
def test_uniform_stretch_paths_limited_by_z(self, spath): result = fit_paths_into_box([spath], (6, 6, 6)) box = bbox(result) assert box.size == (2, 4, 6)
def test_precise_box(self, quadratic): result = bbox([quadratic], flatten=0.01) assert result.extmax.y == pytest.approx(0.5) # parabola
def test_path_coordinates_for_positive_size(self, size, ff): paths = text2path.make_paths_from_str("X", font=ff, size=size) bbox = path.bbox(paths) assert bbox.extmax.y == pytest.approx(size) assert bbox.extmin.y == pytest.approx(0)
def test_non_uniform_stretch_paths(self, spath): result = fit_paths_into_box([spath], (8, 7, 6), uniform=False) box = bbox(result) assert box.size == (8, 7, 6)
def test_one_path(self): p = Path() p.line_to((1, 2, 3)) assert bbox([p]).size == (1, 2, 3)
def test_project_into_yz(self, spath): result = fit_paths_into_box([spath], (0, 6, 6), uniform=False) box = bbox(result) assert box.size == (0, 6, 6), "x-axis should be ignored"
def test_project_into_xz(self, spath): result = fit_paths_into_box([spath], (6, 0, 6)) box = bbox(result) assert box.size == (2, 0, 6), "y-axis should be ignored"
def get_path_bbox(text): paths = text2path.make_paths_from_entity(text) return path.bbox(paths, flatten=0)
def test_text_path_height_for_exact_drawing_units(self, size, ff): paths = text2path.make_paths_from_str("X", font=ff, size=size) bbox = path.bbox(paths) assert bbox.size.y == pytest.approx(abs(size))
def test_project_into_xy(self, spath): result = fit_paths_into_box([spath], (6, 6, 0)) box = bbox(result) # Note: z-axis is also ignored by extent detection: # scaling factor = 3x assert box.size.isclose((3, 6, 0)), "z-axis should be ignored"
def test_two_path(self): p1 = Path() p1.line_to((1, 2, 3)) p2 = Path() p2.line_to((-3, -2, -1)) assert bbox([p1, p2]).size == (4, 4, 4)
def test_project_into_yz(self, spath): result = fit_paths_into_box([spath], (0, 6, 6)) box = bbox(result) assert box.size.isclose((0, 4, 6)), "x-axis should be ignored"
def test_not_precise_box(self, quadratic): result = bbox([quadratic], flatten=0) assert result.extmax.y == pytest.approx(1) # control point
def test_non_uniform_shrink_paths(self, spath): result = fit_paths_into_box([spath], (1.5, 1.5, 1.5), uniform=False) box = bbox(result) assert box.size == (1.5, 1.5, 1.5)
def test_path_coordinates_for_negative_size(self, size, ff): # Negative text height mirrors text about the x-axis! paths = text2path.make_paths_from_str("X", font=ff, size=size) bbox = path.bbox(paths) assert bbox.extmax.y == pytest.approx(0) assert bbox.extmin.y == pytest.approx(size)
# 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_empty_paths(self): result = bbox([]) assert result.has_data is False