to center the text on the page you should set the align parameter to center and set the x coordinate of the text to half of the page width:
text(txt, (width()/2, 70), align ='center')
to center the text on the page you should set the align parameter to center and set the x coordinate of the text to half of the page width:
text(txt, (width()/2, 70), align ='center')
@Anna hi
do you get error messages?
if so you should post them.
and maybe post some code.
what is going wrong and what would you like it to do?
i hope i am not missing this in the documentation …
I was wondering if there is a way to list all instances of a variable font? and then possibly get the values of the axes at these instances.
thanks j
You cannot draw a circle with Beziers.
When I first read this some time ago I was a bit confused.
Why is this not a circle. It sure looks like one.
After learning a bit more about curve descriptions and calculations I kind of agree but the quote should better say:
You cannot draw a perfect circle with Beziers.
We have a formula to calculate points on a Bezier Curve.
x = (1-t)**3*p1[0] + 3*(1-t)**2*t*t1[0] + 3*(1-t)*t**2*t2[0] + t**3*p2[0]
y = (1-t)**3*p1[1] + 3*(1-t)**2*t*t1[1] + 3*(1-t)*t**2*t2[1] + t**3*p2[1]
p1 and p2 are the two endpoints and t1 and t2 the two tangent points. t is the factor (0-1) at which we want the point on the curve.
On a bezier curve with the following coördinates:
p1 = (1, 0)
p2 = (0, 1)
t1 = (1, tangent_val)
t2 = (tangent_val, 1)
we can calculate the tangent_val if we assume that the point at t=0.5 (halfway the curve) is exactly on the circle. Halfway of a quartercircle is 45 degrees so x and y are both at 1/sqrt(2). If we plug these numbers into the formula we can calculate the tangent value:
1/sqrt(2) = (1-.5)**3*1 + 3*(1-.5)**2*.5*1 + 3*(1-.5)*.5**2*tangent_val + .5**3*0
...
tangent_val = 4*(sqrt(2)-1)/3 ≈ 0.5522847493
With this tangent_val we can calculate other points on the bezier curve. Of which we can calculate the angle in respect to the center point at (0, 0). And with this angle we can use sine and cosine to get the point that would be on a circle.
We can also calculate the distance of the bezier point and see where the offset is the biggest. As we can see the difference is really tiny. With a radius of 1 the biggest off value is 1.0002725295591013 (not even one permille). The difference is hardly visible so I added only the top part of a really long bar chart to make it show.
See the image below:
# -------------------
# settings
pw, ph = 595, 842 #A4
margin = 70
tang = 4*(sqrt(2)-1)/3
r = pw - 2 * margin
density = 90 # amount of points
dia_ = 1 # diameter to draw the points
r_plot = 100000 # this is a magnifying factor to make the tiny difference visible.
r_plot_y = margin - r_plot #radius plotting
c_plot_y = ph - 3 * margin - r # circle plotting
# -------------------
# functions
def get_bezier_p(p1, t1, t2, p2, t):
''' get the point on a bezier curve at t (should be between 0 and 1) '''
x = (1 - t)**3 * p1[0] + 3*(1 - t)**2 * t * t1[0] + 3*(1 - t)* t**2 * t2[0] + t**3 * p2[0]
y = (1 - t)**3 * p1[1] + 3*(1 - t)**2 * t * t1[1] + 3*(1 - t)* t**2 * t2[1] + t**3 * p2[1]
return x, y
def get_dist(p1, p2):
''' returns the distance between two points (x, y)'''
return sqrt((p2[0]-p1[0])**2 + (p2[1]-p1[1])**2)
# -------------------
# drawings
newPage(pw, ph)
fill(1)
rect(0, 0, pw, ph)
translate(margin, margin)
# draw the tangent points
rect(r - dia_, c_plot_y + tang*r - dia_, dia_*2, dia_*2)
rect(tang*r -dia_, c_plot_y + r - dia_, dia_*2, dia_*2)
max_off = 1
for i in range(density+1):
# -------
# bezier
# calculate the factor f
f = i/density
fill(0)
if i == 0 or i == (density): dia = dia_ * 4
else: dia = dia_
# get the coordinates for the point at factor f
x_b, y_b = get_bezier_p( (1, 0), (1, tang), (tang, 1), (0, 1), f )
# get the distance of the point from the center at (0, 0)
r_b = get_dist((0, 0), (x_b, y_b))
max_off = max(max_off, r_b)
# get the angle of the point
angle = atan2(y_b, x_b)
# draw the point and a rect for the bar chart
oval(x_b * r - dia/2, c_plot_y + y_b * r - dia/2, dia, dia)
rect((angle/(pi/2)) * r - dia_/2, r_plot_y, dia_, r_plot * r_b)
# -------
# circle
fill(1, 0, 0)
# get the point on a circle
x_c, y_c = cos(angle), sin(angle)
# draw the point and a rect for the bar chart
oval(x_c * r - dia/2, c_plot_y + y_c * r - dia/2, dia, dia)
rect((angle/(pi/2)) * r - dia_/2, r_plot_y, dia_, r_plot)
fill(.3)
if i % 10 == 0:
x_p, y_p = r * f, margin #* 1.5
rect(x_p - .25, y_p, .5, 30)
text('%d°' % (f * 90), (x_p-2, y_p * 1.5))
print (max_off)
text('%.6f' % max_off, (-50, max_off * r_plot - r_plot + margin - 2))
text('%.6f' % 1, (-50, margin - 2))
The maybe more interesting peculiarity of bezier curves is the difference between time and distance. We can compare the bezier distance at factor f (0-1) to the circle if we use the same factor to get the appropriate fraction of 90 degrees.
# -------------------
# settings
pw, ph = 595, 842 #A4
margin = 70
tang = 4*(sqrt(2)-1)/3
r = pw - 2 * margin
density = 90 # amount of points
dia_ = 1 # diameter to draw the points
r_plot = 100000 # this is a magnifying factor to make the tiny difference visible.
r_plot_y = margin - r_plot #radius plotting
c_plot_y = ph - 3 * margin - r # circle plotting
# -------------------
# functions
def get_bezier_p(p1, t1, t2, p2, t):
''' get the point on a bezier curve at t (should be between 0 and 1) '''
x = (1 - t)**3 * p1[0] + 3*(1 - t)**2 * t * t1[0] + 3*(1 - t)* t**2 * t2[0] + t**3 * p2[0]
y = (1 - t)**3 * p1[1] + 3*(1 - t)**2 * t * t1[1] + 3*(1 - t)* t**2 * t2[1] + t**3 * p2[1]
return x, y
def get_dist(p1, p2):
''' returns the distance between two points (x, y)'''
return sqrt((p2[0]-p1[0])**2 + (p2[1]-p1[1])**2)
# -------------------
# drawings
newPage(pw, ph)
fill(1)
rect(0, 0, pw, ph)
translate(margin, margin)
# draw the tangent points
rect(r - dia_, c_plot_y + tang*r - dia_, dia_*2, dia_*2)
rect(tang*r -dia_, c_plot_y + r - dia_, dia_*2, dia_*2)
for i in range(density+1):
# -------
# bezier
# calculate the factor f
f = i/density
fill(0)
if i == 0 or i == (density): dia = dia_ * 4
else: dia = dia_
# get the coordinates for the point at factor f
x_b, y_b = get_bezier_p( (1, 0), (1, tang), (tang, 1), (0, 1), f )
# get the distance of the point from the center at (0, 0)
r_b = get_dist((0, 0), (x_b, y_b))
# get the angle of the point
angle = atan2(y_b, x_b)
# draw the point and a rect for the bar chart
oval(x_b * r - dia/2, c_plot_y + y_b * r - dia/2, dia, dia)
rect((angle/(pi/2)) * r - dia_/2, r_plot_y, dia_, r_plot)
# -------
# circle
fill(1, 0, 0)
# get the point on a circle
x_c, y_c = cos( pi/2*f ), sin( pi/2*f )
# draw the point and a rect for the bar chart
oval(x_c * r - dia/2, c_plot_y + y_c * r - dia/2, dia, dia)
rect(f * r - dia_/2, r_plot_y, dia_, r_plot)
fill(.3)
if i % 10 == 0:
x_p, y_p = r * f, margin #* 1.5
rect(x_p - .25, y_p, .5, 30)
text('%d°' % (f * 90), (x_p-2, y_p * 1.5))
oh, yes!
that is of course a very nice solution.
will update some of the scripts in the next days.
thanks!
@frederik thank you!
There is one 'issue' that makes it difficult to abstract the code. And I was wondering if this could be implemented differently in drawbot.
when setting the fontVariations the axis tag cannot be a variable:
fontVariations( axisTag = someValue )
It would be quite convenient to do:
myAxis = 'wght' # or whatever
# ... do some stuff with myAxis
# ... and then just place the variable name
fontVariations( myAxis = someValue )
or to just iterate through axes without ever having to type the axistag.
After a request to add more axes to the previous simple variable fonts tutorial the follow up escalated a bit. Since there are several files I thought it might be better to put them on github.
@hanafolkwang hi, this is half a year too late and probably not even what you were looking for but I made a few more examples to generate .gifs of variable fonts.
Since there are several files I put them on github.
thanks @gferreira and @frederik!
great help. i managed to botch my rough attempt at type on a curve.
# --------------------------------
# imports
from fontTools.pens.basePen import BasePen
# --------------------------------
# settings
pw = ph = 400
r = 89
angle = pi * 3.1
baseline = ph/2 + r
txt = 'HELLO'
tang_f = .333 # tangent factor
center = pw/2, baseline
focus = pw/2, baseline - r
# --------------------------------
# functions
def ip(a, b, f): return a + (b-a) * f
def conv_point(p):
x, y = p
f = (x - pw/2) / (pw/2)
a = -angle/2 * f
x_conv = center[0] + cos(a + pi/2) * (r + y - baseline)
y_conv = center[1] + sin(a + pi/2) * (r + y - baseline) - r
return x_conv, y_conv
class convert_path_pen(BasePen):
def __init__(self, targetPen):
self.targetPen = targetPen
def _moveTo(self, pt):
self.targetPen.moveTo( conv_point(pt) )
self.prev_pt = pt
def _lineTo(self, pt):
if self.prev_pt[0] == pt[0]:
self.targetPen.lineTo( conv_point(pt) )
else:
tang_1 = ip(self.prev_pt[0], pt[0], tang_f), ip(self.prev_pt[1], pt[1], tang_f)
tang_2 = ip(self.prev_pt[0], pt[0], 1-tang_f), ip(self.prev_pt[1], pt[1], 1-tang_f)
self.targetPen.curveTo(conv_point(tang_1), conv_point(tang_2), conv_point(pt))
self.prev_pt = pt
def _curveToOne(self, tang_1, tang_2, pt):
self.targetPen.curveTo(conv_point(tang_1), conv_point(tang_2), conv_point(pt))
self.prev_pt = pt
def _closePath(self):
self.targetPen.closePath()
# --------------------------------
# drawings
path = BezierPath()
path.text(txt, font="Helvetica", fontSize = 80, align = 'center', offset=(0, baseline))
newPage(pw, ph)
trans_path = BezierPath()
trans_pen = convert_path_pen( trans_path )
path.drawToPen( trans_pen )
drawPath(trans_path)
obligatory gifs
Is there some example code on how to feed a BezierPath
into a custom pen (where I manipulate the coordinates with a function and change the _lineTo
) and then draw that transformed path in my canvas?
thanks, j