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
Posts made by jo

listInstances(font)?

Drawing a circle with Beziers
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.
Getting the best tangent values.
We have a formula to calculate points on a Bezier Curve.
x = (1t)**3*p1[0] + 3*(1t)**2*t*t1[0] + 3*(1t)*t**2*t2[0] + t**3*p2[0] y = (1t)**3*p1[1] + 3*(1t)**2*t*t1[1] + 3*(1t)*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 (01) 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:
Code
#  # 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_p2, 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 (01) to the circle if we use the same factor to get the appropriate fraction of 90 degrees.
Code
#  # 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_p2, y_p * 1.5))

RE: More variable fonts nonsense
oh, yes!
that is of course a very nice solution.
will update some of the scripts in the next days.
thanks! 
RE: More variable fonts nonsense
@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.

More variable fonts nonsense
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.

RE: Tutorial request: how to animate a variable font
@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. 
RE: Tutorial request: BezierPath() to pen
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 + (ba) * 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], 1tang_f), ip(self.prev_pt[1], pt[1], 1tang_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

Tutorial request: BezierPath() to pen
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

RE: Animated MüllerLyer illusion
ok, one more.
very similar to the previous one.page_s = 500 cell_amount = 12 rhythm = [1, 0, 0, 1, 0, 1, 1, 0] cell_s = page_s / cell_amount corner_s = cell_s/4 newPage(page_s, page_s) translate(cell_s/2, cell_s/2) def lozenge(pos, s): x, y = pos polygon((x, y), (x + s/2, y + s/2), (x + s, y), (x + s/2, y  s/2)) for x in range(cell_amount+1): for y in range(cell_amount+1): pos_x, pos_y = x * cell_s, y * cell_s fill(0.5) if (x+y) % 2 == 0 else fill(1) rect(pos_x, pos_y, cell_s, cell_s) fill(1) lozenge((pos_x  corner_s, pos_y), corner_s*2) fill(0) shift = (x + y) % len(rhythm) if rhythm[ shift ]: lozenge((pos_x  corner_s, pos_y), corner_s) lozenge((pos_x, pos_y), corner_s) else: lozenge((pos_x  corner_s/2, pos_y + corner_s/2), corner_s) lozenge((pos_x  corner_s/2, pos_y  corner_s/2), corner_s)

RE: Animated MüllerLyer illusion
nice, ones!
here is one more:grid = 12 page_s = 600 frames = 48 tile_s = page_s/grid rhythm = [1, 0, 0, 1, 0, 1, 1, 0] def cross(pos, s): x, y = pos polygon((x  s/2, y), (x  s/2 + s/8, y  s/8), (x  s/8, y  s/8), (x  s/8, y  s/2 + s/8), (x, y  s/2), (x + s/8, y  s/2 + s/8), (x + s/8, y  s/8), (x + s/2  s/8, y  s/8), (x + s/2, y), (x + s/2  s/8, y + s/8), (x + s/8, y + s/8), (x + s/8, y + s/2  s/8), (x, y + s/2), (x  s/8, y + s/2  s/8), (x  s/8, y + s/8), (x  s/2 + s/8, y + s/8) ) for f in range(frames): newPage(page_s, page_s) frameDuration(.1) # linear alpha = f / frames if f < (frames) else 2  (f / frames) # sinus wave alpha = .5 + .5 * sin(f/frames * 2 * pi) for y in range(grid): for x in range(grid): fill(.8) if (x+y) % 2 == 0 else fill(.7) rect(x * tile_s, y * tile_s, tile_s, tile_s) shift = (x + y) % len(rhythm) fill(1, alpha) if rhythm[ shift ] else fill(.5, alpha) cross((x * tile_s, y * tile_s), tile_s/3 ) # saveImage('wavy_checkerboard.gif')

Animated MüllerLyer illusion
nautil.us has a nice blogpost of optical illusions. I could not resist the attempt to build one of them in drawbot.
pw = ph = 400 # define variables to set width and height of the canvas cols = 14 # define how many columns there should be rows = 4 # define how many rows there should be pages = 30 # define how many frames the gif should have. more pages mean slower movement angle_diff = 2 * pi/pages # we want the loop to make one full circle. 2 times pi is 360 degrees. Divided by the amount of pages this gives us the angle difference between pages. gap = pw/(cols+1) # calculate the gap between the lines. l = (ph  2 * gap)/rows # calculate the length of the line. for p in range(pages): newPage(pw, ph) translate(gap, gap) fill(None) strokeWidth(2) for x in range(cols): y_off = cos( 3 * pi * x/cols + angle_diff * p ) * gap/2 # calculate how high or low the arrowhead should be for y in range(rows+1): stroke(1,0 ,0) if y % 2 == 0 else stroke(0, 0, 1) if y < rows: line((x*gap, y*l), (x*gap, y*l+l)) stroke(0) y_off *= 1 # switch to change direction line( (x*gap, y*l), (x*gap  gap/2, y*l  y_off) ) line( (x*gap, y*l), (x*gap + gap/2, y*l  y_off) ) # saveImage('~/Desktop/opti_illu.gif')
this should generate something like this:

RE: Installing Packages
@rafalbuchner did you try to append the directory to the sys.path?
sys.path.append('/usr/local/lib/python3.6/sitepackages/')

RE: Tutorial request: how to animate a variable font
@mikedug hello mike,
if you want higher resolution you just increase the values for width and height withinnewPage()
.In case you are looking for better quality and not higher resolution you might want to generate separate pngs first and then use
ffmpeg
from the command line to generate a gif. ffmpeg has a lot of settings,
this link might be a starting point. 
RE: Animation tutorial screencast
@longseason
at what part are you stuck?did you have a look at the documentation?
http://www.drawbot.com/content/text/drawingText.html 
RE: A deeper bevel on lineJoin()?
@mauricemeilleur sorry. I made the changes and updated the post. Only after implementing the changes in my local version did I realize that i forgot to adapt the
len(points) == 2
option. but great you figured it out anyway,
good luck! 
RE: A deeper bevel on lineJoin()?
and thanks to @gferreira who helped me getting the initial bits!

RE: A deeper bevel on lineJoin()?
@mauricemeilleur I don’t know how Crouwel defined his rules. but to me the cornerrule seems like an integral part of the design.
I had a look at my code and tried to tidy the worst bits. some of the angle calculations seem very inefficient but ¯\(ツ)/¯ here it is:
EDIT 20180528 changed the code to simplify some of the horrible trigonometry and there now is the
sq_cap
bool to have squared linecaps.# Draw a 'folded outline' between a set of points. # TO DO: There should be an error message when two dots are closer to each other and the weight. # There should be a warning if the folding is further than the next points. # There should be a warning for collinear points. import math # Just a bunch of points p01 = (894, 334) p02 = (808, 86) p03 = (274, 792) p04 = (481, 920) p05 = (583, 730) p06 = (85, 430) p07 = (318, 100) p08 = (870, 600) p09 = (720, 690) points = [p01, p02, p03, p04, p05, p06, p07, p08, p09] def get_dist(p1, p2): return sqrt( (p2[0]  p1[0] )**2 + (p2[1]  p1[1])**2 ) def calc_angle(p1, p2): return atan2((p2[1]  p1[1]), (p2[0]  p1[0])) def calc_delta(p1, p2, p3): ''' Returns the angle between three points. ''' return calc_angle(p2, p3)  calc_angle(p2, p1) def calc_offset(weight, p1, p2, p3): ''' Returns the x and y "offset" to be added to the points of the polygon. ''' halfWeight = weight/2 if p1 == p2: alpha = calc_angle(p1, p3) b = sin(alpha) * halfWeight a = cos(alpha) * halfWeight elif p2 == p3: alpha = calc_angle(p1, p3) b = sin(alpha) * halfWeight a = cos(alpha) * halfWeight else: alpha = calc_angle(p2, p3) delta = calc_delta(p1, p2, p3) xx = halfWeight / sin((delta  pi) / 2) delta_f = (2 * alpha  pi  delta ) / 2 a, b = xx * sin(delta_f), xx * cos(delta_f) return a, b def wrap_line(p1, p2, p3, p4, weight): ''' This draws a polygon around a line. Four points (x,y) and a weight parameter are required. The polygon will be drawn around the two middle points. The first and last points define the angles. ''' a, b = calc_offset(weight, p1, p2, p3) # extend first point x1b, y1b = p2[0]  b, p2[1]  a x1c, y1c = p2[0] + b, p2[1] + a # extend second point c, d = calc_offset(weight, p2, p3, p4) x2b, y2b = p3[0]  d, p3[1]  c x2c, y2c = p3[0] + d, p3[1] + c polygon((x1b, y1b), (x1c, y1c), (x2b, y2b), (x2c, y2c)) #  # The actual drawing fill(0, .4) weight = 180 sq_cap = True if len(points) == 1: rect(points[0][0]  weight/2, points[0][1]  weight/2, weight, weight) for i, point in enumerate(points[:1]): # print (get_dist(point, points[i+1])) if len(points) == 2: wrap_line(points[i], points[i], points[i+1], points[i+1], weight) else: if i == 0: aaa = calc_angle(points[i], points[i+1]) if sq_cap: point = point[0]  weight/2 * cos(aaa), point[1]  weight/2 * sin(aaa) wrap_line(point, point, points[i+1], points[i+2], weight) elif i == len(points)2: next_p = points[i+1] aaa = calc_angle(points[i], next_p) if sq_cap: next_p = next_p[0] + weight/2 * cos(aaa), next_p[1] + weight/2 * sin(aaa) wrap_line(points[i1], points[i], next_p, next_p, weight) else: # print ( calc_delta(points[i1], points[i], points[i+1]) ) wrap_line(points[i1], points[i], points[i+1], points[i+2], weight)

RE: A deeper bevel on lineJoin()?
I was working on a foldfont system a few years ago. it took some time to get the trigonometry right. but once that was solved the possibilities were endless ...
so the new alphabet could be rendered with different stroke weights
scaling in the x
or y direction
and there could be multiliners
seems like i did not compensate for the stroke weight at the end of a stroke but the system could be used for any collection of xypoints
it is all a bit messy but let me know if you need some bits.

setPixelColor() image Object?
hi,
maybe I am not able to find it in the documentation.
But is there a way to set the pixel color at (x,y) of an image object?
thanks j 
RE: Collinearity of points
what would be 'compare tangents'?
—
comparing angles is probably more expensive?def get_angle(p1, p2): return atan2((p2[1]  p1[1]), (p2[0]  p1[0])) pts = [ (725,417), (548, 440), (414, 458), (261, 479) ] angles = [ get_angle(point, pts[p+1]) for p, point in enumerate(pts[:1])] angle = get_angle(pts[0], pts[1]) # tolerance for error margin = pi/180 fontSize(100) if all(abs(a  angle) < margin for a in angles): text("colinear",(100,100)) else: text("not colinear",(100,100)) # draw the dots fill(0,0,1) stroke(1,0,0) translate(100,100) newPath() moveTo(pts[0]) for p in pts[1:]: lineTo(p) drawPath() ds = 10 fill(0,0,1) stroke(None) for p in pts: oval(p[0].5*ds, p[1].5*ds, ds, ds)