add text to hpgl tab
This commit is contained in:
105
text_to_hpgl.py
Normal file
105
text_to_hpgl.py
Normal file
@@ -0,0 +1,105 @@
|
||||
"""
|
||||
Convert text to HPGL using a font file (.otf, .ttf).
|
||||
Uses matplotlib TextPath; flattens curves to line segments.
|
||||
"""
|
||||
from __future__ import division
|
||||
import os
|
||||
|
||||
# Path codes from matplotlib.path.Path
|
||||
MOVETO = 1
|
||||
LINETO = 2
|
||||
CURVE3 = 3
|
||||
CURVE4 = 4
|
||||
CLOSEPOLY = 79
|
||||
|
||||
|
||||
def _flatten_quadratic(p0, p1, p2, n=10):
|
||||
"""Quadratic Bezier: B(t) = (1-t)^2 P0 + 2(1-t)t P1 + t^2 P2."""
|
||||
for i in range(1, n + 1):
|
||||
t = i / n
|
||||
u = 1 - t
|
||||
x = u * u * p0[0] + 2 * u * t * p1[0] + t * t * p2[0]
|
||||
y = u * u * p0[1] + 2 * u * t * p1[1] + t * t * p2[1]
|
||||
yield (x, y)
|
||||
|
||||
|
||||
def _flatten_cubic(p0, p1, p2, p3, n=10):
|
||||
"""Cubic Bezier: B(t) = (1-t)^3 P0 + 3(1-t)^2 t P1 + 3(1-t)t^2 P2 + t^3 P3."""
|
||||
for i in range(1, n + 1):
|
||||
t = i / n
|
||||
u = 1 - t
|
||||
x = u * u * u * p0[0] + 3 * u * u * t * p1[0] + 3 * u * t * t * p2[0] + t * t * t * p3[0]
|
||||
y = u * u * u * p0[1] + 3 * u * u * t * p1[1] + 3 * u * t * t * p2[1] + t * t * t * p3[1]
|
||||
yield (x, y)
|
||||
|
||||
|
||||
def text_to_hpgl(text, font_path, size_pt=72, units_per_pt=14):
|
||||
"""
|
||||
Convert text to HPGL string using the given font file.
|
||||
size_pt: font size in points.
|
||||
units_per_pt: HPGL units per point (default ~0.35 mm per pt).
|
||||
Returns HPGL string (IN;SP1;PU x,y;PD;PA x,y;...).
|
||||
"""
|
||||
try:
|
||||
from matplotlib.textpath import TextPath
|
||||
from matplotlib.font_manager import FontProperties
|
||||
except ImportError:
|
||||
raise RuntimeError('matplotlib is required for text-to-HPGL. Install with: pip install matplotlib')
|
||||
|
||||
if not text or not font_path or not os.path.isfile(font_path):
|
||||
raise ValueError('Need non-empty text and a valid font file path.')
|
||||
|
||||
prop = FontProperties(fname=font_path)
|
||||
path = TextPath((0, 0), text, size=size_pt, prop=prop)
|
||||
vertices = path.vertices
|
||||
codes = path.codes
|
||||
|
||||
if codes is None:
|
||||
codes = [MOVETO] + [LINETO] * (len(vertices) - 1)
|
||||
|
||||
# Scale to HPGL units (y flip: matplotlib y up, HPGL typically y up too; we keep same)
|
||||
scale = units_per_pt
|
||||
segments = []
|
||||
i = 0
|
||||
contour_start = None
|
||||
|
||||
while i < len(codes):
|
||||
code = codes[i]
|
||||
if code == MOVETO:
|
||||
x, y = vertices[i]
|
||||
segments.append(('PU', int(round(x * scale)), int(round(y * scale))))
|
||||
contour_start = (int(round(x * scale)), int(round(y * scale)))
|
||||
i += 1
|
||||
elif code == LINETO:
|
||||
x, y = vertices[i]
|
||||
segments.append(('PD', int(round(x * scale)), int(round(y * scale))))
|
||||
i += 1
|
||||
elif code == CLOSEPOLY:
|
||||
if contour_start:
|
||||
segments.append(('PD', contour_start[0], contour_start[1]))
|
||||
contour_start = None
|
||||
i += 1
|
||||
elif code == CURVE3 and i + 1 < len(vertices):
|
||||
for (x, y) in _flatten_quadratic(
|
||||
(vertices[i - 1][0], vertices[i - 1][1]),
|
||||
(vertices[i][0], vertices[i][1]),
|
||||
(vertices[i + 1][0], vertices[i + 1][1]),
|
||||
):
|
||||
segments.append(('PD', int(round(x * scale)), int(round(y * scale))))
|
||||
i += 2
|
||||
elif code == CURVE4 and i + 2 < len(vertices):
|
||||
p0, p1, p2, p3 = vertices[i - 1], vertices[i], vertices[i + 1], vertices[i + 2]
|
||||
for (x, y) in _flatten_cubic(p0, p1, p2, p3):
|
||||
segments.append(('PD', int(round(x * scale)), int(round(y * scale))))
|
||||
i += 3
|
||||
else:
|
||||
i += 1
|
||||
|
||||
out = ['IN', 'SP1']
|
||||
for seg in segments:
|
||||
if seg[0] == 'PU':
|
||||
out.append('PU{},{}'.format(seg[1], seg[2]))
|
||||
else:
|
||||
out.append('PD{},{}'.format(seg[1], seg[2]))
|
||||
out.append('PU')
|
||||
return ';'.join(out) + ';'
|
||||
Reference in New Issue
Block a user