-
Notifications
You must be signed in to change notification settings - Fork 683
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Select PS Eraser points properly for over_exclude (cf #2689) #4191
base: master
Are you sure you want to change the base?
Conversation
@skef By the way: I have written an own PS-import function in python for FontForge in https://github.com/linusromer/mf2outline/blob/master/mf2outline.py which is far from complete. However, it works quite well with EPS from MetaPost. It uses the python package # own postscript interpreter for a postscript file "eps"
# into the glyph "glyph"
def import_ps(eps,glyph):
from booleanOperations import BooleanOperationManager
from booleanOperations.booleanGlyph import BooleanGlyph
with open(eps, "r") as epsfile:
# read through the lines and write them continously as contours
# in a fontforge char
# this is specialized for Metapost Output and useless for
# general postscript
#
# some variable declarations
rawglyph = [] # will be filled with raw contours
is_white = False # any other color than white will be interpreted as black
linewidth = 1 # just default
stack = [] # ps stack (coordinate of points, pen dimensions etc.)
ctm = [1.0,0.0,0.0,1.0,0.0,0.0] # current transformation matrix
dash = 0
linecap = "round"
linejoin = "round"
miterlimit = 10
contour = []
stroke_follows_fill = False
gsave_contour = [] # (list) bezier path that is saved by gsave
gsave_ctm = [1.0,0.0,0.0,1.0,0.0,0.0] # current transformation matrix that is saved by gsave
gsave_is_white = False # "color" saved by gsave
#
# okay, let's start:
for line in epsfile:
if line[0] != "%" and len(line)>0: # ignore comments
if "gsave fill grestore stroke" in line: # this happens just so often, so we treat it as special case
stroke_follows_fill = True # pay attention...
words = line.split()
for word in words: # go through the words
# showpage will have no effect
if isolate_number(word) != None:
stack.append(isolate_number(word))
elif word == "newpath":
contour = [] # (list) bezier path
elif (word == "moveto") or (word == "lineto"):
contour.append([(stack[-2],stack[-1])])
stack = stack[:-2]
elif word == "curveto":
contour.append([
(stack[-6],stack[-5]),
(stack[-4],stack[-3]),
(stack[-2],stack[-1])
])
stack = stack[:-6]
elif word == "closepath":
if (len(contour) > 1) and \
(contour[0][0][0] != contour[-1][-1][0]) or \
(contour[0][0][1] != contour[-1][-1][1]):
contour.append([(contour[0][0][0],contour[0][0][1])])
elif word == "setrgbcolor":
if stack[-1] == 1 and \
stack[-2] == 1 and \
stack[-3] == 1:
is_white = True
else:
is_white = False
stack = stack[:-3]
elif word == "setlinewidth":
linewidth = stack.pop()
elif word == "setdash":
dash = stack.pop()
elif word == "setlinecap":
linecapcode = stack.pop()
if linecapcode == 0:
linecap = "butt"
elif linecapcode == 2:
linecap = "square"
else:
linecap = "round"
elif word == "setlinejoin":
linejoincode = stack.pop()
if linejoincode == 0:
linejoin = "miter"
elif linejoincode == 2:
linejoin = "bevel"
else:
linejoin = "round"
elif word == "setmiterlimit":
miterlimit = stack.pop()
elif word == "scale":
sy=stack.pop()
sx=stack.pop()
ctm=[ctm[0]*sx,ctm[1]*sy,ctm[2]*sx,ctm[3]*sy,ctm[4]*sx,ctm[5]*sy]
elif word == "concat":
f=stack.pop()
e=stack.pop()
d=stack.pop()
c=stack.pop()
b=stack.pop()
a=stack.pop()
ctm=[a*ctm[0]+c*ctm[1],b*ctm[0]+d*ctm[1],\
a*ctm[2]+c*ctm[3],b*ctm[2]+d*ctm[3],\
a*ctm[4]+c*ctm[5]+e,b*ctm[4]+d*ctm[5]+f]
elif word == "dtransform":
dy=stack.pop()
dx=stack.pop()
stack.extend(homogeneous(ctm[:4],dx,dy))
elif word == "idtransform":
dy=stack.pop()
dx=stack.pop()
stack.extend(homogeneous(invertmatrix(ctm[:4]),dx,dy))
elif word == "transform":
y=stack.pop()
x=stack.pop()
stack.extend(homogeneous(ctm,x,y))
elif word == "itransform":
y=stack.pop()
x=stack.pop()
stack.extend(homogeneous(invertmatrix(ctm),x,y))
elif word == "exch":
last = stack.pop()
secondlast = stack.pop()
stack.extend([last,secondlast])
#elif word == "truncate":
# truncate is problematic, as it produces
# results that are not exact
# I do not know, why this is in METAPOST
# stack[len(stack)-1]=int(stack[len(stack)-1])
elif word == "pop":
stack.pop()
elif word == "fill" and not stroke_follows_fill:
if is_white:
if windingnumber(contour) > 0:
contour = bezierreverse(contour) # make counterclockwise
rawglyph = rawDifference(rawglyph,[contour])
else:
if windingnumber(contour) < 0:
contour = bezierreverse(contour) # make clockwise
rawglyph.append(contour) #rawglyph = romerUnion(rawglyph,[contour])
elif word == "stroke":
# We have to determine the angle and the
# axis of the ellipse that is the product of
# a circle with radius=linewidth with the
# ctm (current transformation matrix.
# One would have to consider also shearing,
# but METAPOST makes the ctm (for pens)
# always as a product of rotation matrix and
# a diagonal (non-uniform scaling) matrix.
# Hence, we make a quick an dirty computation
if ctm[0] == ctm[1]:
alpha = .25*math.pi
else:
alpha = math.atan(ctm[1]/ctm[0])
pen_x = abs(linewidth*ctm[0]/math.cos(alpha))
pen_y = abs(linewidth*ctm[3]/math.cos(alpha))
if stroke_follows_fill:
tempcontour = bezierouteroutline(contour,pen_x,pen_y,alpha)
stroke_follows_fill = False
else:
tempcontour = bezieroutline(contour,pen_x,pen_y,alpha)
if is_white:
if windingnumber(tempcontour) > 0:
tempcontour = bezierreverse(tempcontour) # make counterclockwise
rawglyph = rawDifference(rawglyph,[tempcontour])
else:
if windingnumber(tempcontour) < 0:
tempcontour = bezierreverse(tempcontour) # make clockwise
rawglyph.append(tempcontour) #rawglyph = romerUnion(rawglyph,[tempcontour])
elif word == "gsave":
gsave_contour = list(contour) # clone contour
gsave_ctm = ctm
gsave_is_white = is_white
elif word == "grestore":
contour = list(gsave_contour)
ctm = gsave_ctm
is_white = gsave_is_white
# now fill the raw glyph into a fontforge glyph:
#rawglyphrounded = roundRawGlyph(rawglyph,10)
for c in rawglyph:
glyph.foreground += rawPathToFontforgeContour(c)
if not args.raw:
glyph.removeOverlap() |
The missing corner rounding should be fixed by #4183, which is waiting on review. FontForge's exclude function does appear to act strangely with nested contours. From a very quick look based on your examples it appears that
These aren't difficult changes so I'll give it a shot and update the draft PR, probably sometime this evening. Problem 2 seems like a bug separate from PS input, though, so once I confirm the fix is possible I'll ask about potentially changing the main algorithm. |
(I merged #4183 and rebased to fix the join problem.) @linusromer I worked around the immediate problems by running Remove Overlap on both SplineSets separately before running Exclude. The three examples now work as shown in your illustration. I realize that incrementally debugging FontForge's implementation may not be of much interest but unless and until I can track down a proper reference on this subject you're my main resource. Still, feel free to bow out now or whenever. |
@skef Yes, the three old EPS work now (and the rounded corners, too). However, the following still won't work: |
@skef, what remains on this one? |
I think this one might remain a draft for a while. The current version represents progress but it's not done and I'm not sure its a priority. |
Closes #2689