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