1# Copyright 2015 Google Inc. All Rights Reserved. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15 16"""Mitre Glyph: 17 18mitreSize : Length of the segment created by the mitre. The default is 4. 19maxAngle : Maximum angle in radians at which segments will be mitred. The default is .9 (about 50 degrees). 20 Works for both inside and outside angles 21 22""" 23 24import math 25from robofab.objects.objectsRF import RPoint, RSegment 26from fontbuild.convertCurves import replaceSegments 27 28def getTangents(contours): 29 tmap = [] 30 for c in contours: 31 clen = len(c) 32 for i in range(clen): 33 s = c[i] 34 p = s.points[-1] 35 ns = c[(i + 1) % clen] 36 ps = c[(clen + i - 1) % clen] 37 np = ns.points[1] if ns.type == 'curve' else ns.points[-1] 38 pp = s.points[2] if s.type == 'curve' else ps.points[-1] 39 tmap.append((pp - p, np - p)) 40 return tmap 41 42def normalizeVector(p): 43 m = getMagnitude(p); 44 if m != 0: 45 return p*(1/m) 46 else: 47 return RPoint(0,0) 48 49def getMagnitude(p): 50 return math.sqrt(p.x*p.x + p.y*p.y) 51 52def getDistance(v1,v2): 53 return getMagnitude(RPoint(v1.x - v2.x, v1.y - v2.y)) 54 55def getAngle(v1,v2): 56 angle = math.atan2(v1.y,v1.x) - math.atan2(v2.y,v2.x) 57 return (angle + (2*math.pi)) % (2*math.pi) 58 59def angleDiff(a,b): 60 return math.pi - abs((abs(a - b) % (math.pi*2)) - math.pi) 61 62def getAngle2(v1,v2): 63 return abs(angleDiff(math.atan2(v1.y, v1.x), math.atan2(v2.y, v2.x))) 64 65def getMitreOffset(n,v1,v2,mitreSize=4,maxAngle=.9): 66 67 # dont mitre if segment is too short 68 if abs(getMagnitude(v1)) < mitreSize * 2 or abs(getMagnitude(v2)) < mitreSize * 2: 69 return 70 angle = getAngle2(v2,v1) 71 v1 = normalizeVector(v1) 72 v2 = normalizeVector(v2) 73 if v1.x == v2.x and v1.y == v2.y: 74 return 75 76 77 # only mitre corners sharper than maxAngle 78 if angle > maxAngle: 79 return 80 81 radius = mitreSize / abs(getDistance(v1,v2)) 82 offset1 = RPoint(round(v1.x * radius), round(v1.y * radius)) 83 offset2 = RPoint(round(v2.x * radius), round(v2.y * radius)) 84 return offset1, offset2 85 86def mitreGlyph(g,mitreSize,maxAngle): 87 if g == None: 88 return 89 90 tangents = getTangents(g.contours) 91 sid = -1 92 for c in g.contours: 93 segments = [] 94 needsMitring = False 95 for s in c: 96 sid += 1 97 v1, v2 = tangents[sid] 98 off = getMitreOffset(s,v1,v2,mitreSize,maxAngle) 99 s1 = s.copy() 100 if off != None: 101 offset1, offset2 = off 102 p2 = s.points[-1] + offset2 103 s2 = RSegment('line', [(p2.x, p2.y)]) 104 s1.points[0] += offset1 105 segments.append(s1) 106 segments.append(s2) 107 needsMitring = True 108 else: 109 segments.append(s1) 110 if needsMitring: 111 replaceSegments(c, segments) 112