Source code for fibomat.shapes.text

from typing import Optional

import numpy as np

import pyhershey

from fibomat.shapes import Polyline, Polygon
from fibomat.layout import Group, DimGroup
from fibomat.linalg import Vector, angle_between, translate
from fibomat.linalg.helpers import make_perp_vector, GeomLine
from fibomat.units import U_
# from fibomat.curve_tools import


[docs]class Text(Group): """""" @staticmethod def _add_stroke(polyline: Polyline, stroke_width: float) -> Polygon: def make_normals(p1: Vector, p2: Vector): normal = make_perp_vector(p2-p1).normalized_to(stroke_width) return normal, -1 * normal def make_offset_segments(p1: Vector, p2: Vector): normals = make_normals(p1, p2) length = np.linalg.norm(p1 - p2) return ( GeomLine(Vector((p2 + normals[0]) - (p1 + normals[0])).normalized_to(length), (p1 + normals[0])), GeomLine(Vector((p2 + normals[1]) - (p1 + normals[1])).normalized_to(length), (p1 + normals[1])) ) def make_points(segments_, prev_segments_): if segments_[0].parallel_to(prev_segments_[0]): return [segments_[0](0)], [segments_[1](0)] else: param = segments_[0].intersect_at_param(prev_segments_[0]) if 0 < param < 1: return [segments_[0](param)], [prev_segments_[1](1), segments_[1](0)] else: param = segments_[1].intersect_at_param(prev_segments_[1]) return [prev_segments_[0](1), segments_[0](0)], [segments_[1](param)] polyline_points = polyline.points if len(polyline_points) < 2: raise RuntimeError closed = np.allclose(polyline_points[0], polyline_points[-1]) points_first_side = [] points_second_side = [] first_segments = make_offset_segments(polyline_points[0], polyline_points[1]) if not closed: points_first_side.append(first_segments[0](0)) points_second_side.append(first_segments[1](0)) prev_segments = first_segments for i in range(1, len(polyline_points) - 1): segments = make_offset_segments(polyline_points[i], polyline_points[i+1]) first, second = make_points(segments, prev_segments) points_first_side.extend(first) points_second_side.extend(second) prev_segments = segments if closed: first, second = make_points(first_segments, prev_segments) points_first_side.extend(first) points_second_side.extend(second) points_first_side.insert(0, first[-1]) points_second_side.insert(0, second[-1]) else: points_first_side.append(prev_segments[0](1)) points_second_side.append(prev_segments[1](1)) return Polygon(points_first_side + list(reversed(points_second_side)))
[docs] def __init__( self, text: str, font_size: float = 1, stroke_width: Optional[float] = None, text_alignment: Optional[str] = None, description: Optional[str] = None ): """ Args: text (str): text font_size (float, optional): font size (directly correspond to the height of upper case letters). Default to 1. stroke_width (float, optional): the stroke width of the glyph segments. If None, the glyph segments are given by 1d polylines. Default to None. text_alignment (str, optional): the text alignment. Can be "left", "center", "right". Default to "left". """ font_size /= 21 advance_height = 1.6 * 21 * font_size if not text_alignment: text_alignment = 'left' shaped_text_lines = [ pyhershey.shape_text( text_line, advance_height=0, mapping='roman_simplex', font_size=font_size, text_align=text_alignment ) for text_line in text.split('\n') ] glyph_groups = [] y_shift = 0 anchors = [] for shaped_text_line in shaped_text_lines: for shaped_glyph in shaped_text_line: glyph_polylines = [] for segment in shaped_glyph['glyph'].segments: # if np.allclose(segment[0], segment[-1]): # seg_shape = Polygon(segment) # else: # seg_shape = Polyline(segment) if stroke_width: glyph_polylines.append(self._add_stroke(Polyline(segment), stroke_width)) else: glyph_polylines.append(Polyline(segment)) if glyph_polylines: glyph_groups.append( Group(glyph_polylines).transformed(translate(shaped_glyph['pos']) | translate((0, y_shift))) ) anchor_left = Vector(shaped_text_line[0]['pos']) + (0, y_shift) anchor_right = Vector(shaped_text_line[-1]['pos']) + (shaped_text_line[-1]['glyph'].advance_width, y_shift) anchor_center = Vector((anchor_left.x + anchor_right.x) / 2, y_shift) y_shift -= advance_height anchors.append( {'left': anchor_left, 'center': anchor_center, 'right': anchor_right} ) self._n_lines = len(shaped_text_lines) self._anchors = anchors # glyph_group = Group(glyph_groups) super().__init__(glyph_groups, description)
@property def n_lines(self) -> int: """int: number of text lines""" return self._n_lines
[docs] def baseline_anchor(self, pos: str, i_line: int = 0) -> Vector: """Return position of certain base line points. Args: pos (str): position on the base line. Can be "left", "center" or "right". i_line (int, optional): index of the base line to be used. Can be indexed like any array (0 is the first element, -1 the last etc.). Default to 0. Returns: """ return self._anchors[i_line][pos]
def __mul__(self, other): if isinstance(other, U_): # from fibomat.layout.groups.dim_group import DimGroup return DimText(self.elements, self._anchors, other, description=self.description) raise NotImplementedError
[docs]class DimText(DimGroup):
[docs] def __init__(self, elements, anchors, unit, description): super().__init__([elem * unit for elem in elements], description) self._anchors = anchors self._unit = unit
@property def n_lines(self) -> int: return len(self._anchors)
[docs] def baseline_anchor(self, pos: str, i_line: int = 0) -> Vector: return self._anchors[i_line][pos] * self._unit
# def shape_text( # # ) -> Group: # """Shape a given text. # This create a group containing glyphs where each glyph is given as a subgroup. # ``text`` can be any string but may only contain printable ASCII characters and "°". New lines can be introduced # with "\\n". # # The pivot of the returned group is set to start of the baseline of the first text row. If ``text_align`` is "center" # or "right", the pivot is at the center or right side of the baseline, respectively. # # Args: # text (str): text # font_size (float, optional): # font size (directly correspond to the height of upper case letters). Default to 1. # stroke_width (float, optional): # the stroke width of the glyph segments. If None, the glyph segments are given by 1d polylines. # Default to None. # text_alignment (str, optional): the text alignment. Can be "left", "center", "right". Default to "left". # # Returns: # Group: grouped shaped text as # """ # # font_size /= 21 # advance_height = 1.5 * 21 * font_size # # if not text_alignment: # text_alignment = 'left' # # shaped_glyphs = pyhershey.shape_text( # text, advance_height=advance_height, mapping='roman_simplex', font_size=font_size, text_align=text_alignment # ) # # glyph_groups = [] # # for shaped_glyph in shaped_glyphs: # glyph_polylines = [] # # for segment in shaped_glyph['glyph'].segments: # if stroke_width: # glyph_polylines.append(_add_stroke(Polyline(segment), stroke_width)) # else: # glyph_polylines.append(Polyline(segment)) # # if glyph_polylines: # glyph_groups.append(Group(glyph_polylines).translated(shaped_glyph['pos'])) # # glyph_group = Group(glyph_groups) # # glyph_group.pivot = # # return glyph_group.translated_to((0, 0))