# coding: utf-8 # Copyright 2013 The Font Bakery Authors. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # See AUTHORS.txt for the list of Authors and LICENSE.txt for the License. # # Contributor: Raph Levien from fontTools.pens.basePen import BasePen from fontTools.ttLib.tables import _g_l_y_f import math from . import quadopt class PDFPen(BasePen): def __init__(self, glyphSet, path): BasePen.__init__(self, glyphSet) self.path = path def _moveTo(self, p): (x,y) = p self.path.moveTo(x,y) def _lineTo(self, p): (x,y) = p self.path.lineTo(x,y) def _curveToOne(self, p1, p2, p3): (x1,y1) = p1 (x2,y2) = p2 (x3,y3) = p3 self.path.curveTo(x1, y1, x2, y2, x3, y3) def lerppt(t, p0, p1): return (p0[0] + t * (p1[0] - p0[0]), p0[1] + t * (p1[1] - p0[1])) def glyph_to_bzs(g): bzs = [] for i in range(g.numberOfContours): beg = 0 if i == 0 else g.endPtsOfContours[i - 1] + 1 end = g.endPtsOfContours[i] + 1 n = end - beg pts = g.coordinates[beg:end] flags = g.flags[beg:end] bz = [] for j in range(n): x1, y1 = pts[(j+1) % n] if flags[j] and flags[(j+1) % n]: bz.append((pts[j], (x1, y1))) elif not flags[j]: if flags[j - 1]: x0, y0 = pts[j - 1] else: x0, y0 = lerppt(0.5, pts[j - 1], pts[j]) if not flags[(j+1) % n]: x1, y1 = lerppt(0.5, (x1, y1), pts[j]) if tuple(pts[j]) == (x0, y0) or tuple(pts[j]) == (x1, y1): # degenerate quad, treat as line bz.append(((x0, y0), (x1, y1))) else: bz.append(((x0, y0), pts[j], (x1, y1))) bzs.append(bz) return bzs def segment_sp(sp): bks = set() # direction changes xsg = 0 ysg = 0 for i in range(2 * len(sp)): imod = i % len(sp) xsg1 = sp[imod][-1][0] - sp[imod][0][0] ysg1 = sp[imod][-1][1] - sp[imod][0][1] if xsg * xsg1 < 0 or ysg * ysg1 < 0: bks.add(imod) xsg = xsg1 ysg = ysg1 else: if xsg == 0: xsg = xsg1 if ysg == 0: ysg = ysg1 # angle breaks for i in range(len(sp)): dx0 = sp[i-1][-1][0] - sp[i-1][-2][0] dy0 = sp[i-1][-1][1] - sp[i-1][-2][1] dx1 = sp[i][1][0] - sp[i][0][0] dy1 = sp[i][1][1] - sp[i][0][1] bend = dx1 * dy0 - dx0 * dy1 if (dx0 == 0 and dy0 == 0) or (dx1 == 0 and dy1 == 0): bks.add(i) else: bend = bend / (math.hypot(dx0, dy0) * math.hypot(dx1, dy1)) # for small angles, bend is in units of radians if abs(bend) > 0.02: bks.add(i) return sorted(bks) def seg_to_string(sp, bk0, bk1): if bk1 < bk0: bk1 += len(sp) res = [] for i in range(bk0, bk1): bz = sp[i % len(sp)] if len(bz) == 2: # just represent lines as quads bz = (bz[0], lerppt(0.5, bz[0], bz[1]), bz[1]) res.append(' '.join(['%g' % z for xy in bz for z in xy]) + '\n') return ''.join(res) def optimize_glyph(glyph, penalty=None): bzs = glyph_to_bzs(glyph) newbzs = [] for sp in bzs: bks = segment_sp(sp) newsp = [] for i in range(len(bks)): bk0, bk1 = bks[i], bks[(i + 1) % len(bks)] if bk1 != (bk0 + 1) % len(sp) or len(sp[bk0]) != 2: segstr = seg_to_string(sp, bk0, bk1) if penalty: optstr = quadopt.optimize(segstr, penalty) else: optstr = quadopt.optimize(segstr) newsp.extend(read_bzs(optstr.strip())) else: newsp.append(sp[bk0]) newbzs.append(newsp) bzs_to_glyph(newbzs, glyph) def read_bzs(segstr): result = [] for l in segstr.split("\n"): z = [float(z) for z in l.split()] bz = ((z[0], z[1]), (z[2], z[3]), (z[4], z[5])) if bz[1] == lerppt(0.5, bz[0], bz[2]): bz = (bz[0], bz[2]) result.append(bz) return result def pt_to_int(pt): # todo: should investigate non-int points return (int(round(pt[0])), int(round(pt[1]))) def bzs_to_glyph(bzs, glyph): coordinates = [] flags = [] endPtsOfContours = [] for sp in bzs: for i in range(len(sp)): lastbz = sp[i - 1] bz = sp[i] if len(bz) != 3 or len(lastbz) != 3 or lerppt(0.5, lastbz[1], bz[1]) != bz[0]: coordinates.append(pt_to_int(bz[0])) flags.append(1) if len(bz) == 3: coordinates.append(pt_to_int(bz[1])) flags.append(0) endPtsOfContours.append(len(coordinates) - 1) glyph.coordinates = _g_l_y_f.GlyphCoordinates(coordinates) glyph.flags = flags glyph.endPtsOfContours = endPtsOfContours def plot_glyph(font, name, canvas, orig): if canvas is None: return from reportlab.lib.colors import CMYKColor gs = font.getGlyphSet() glyph = gs[name] pen = PDFPen(gs, canvas.beginPath()) glyph.draw(pen) if orig: color = CMYKColor(0, 1, 1, 0, alpha=.5) else: color = CMYKColor(0, 0, 0, 1, alpha=.5) x0 = 100 y0 = 100 scale = 0.25 canvas.saveState() canvas.setFillColor(color) canvas.translate(x0, y0) canvas.scale(scale, scale) canvas.drawPath(pen.path, stroke=False, fill=True) canvas.restoreState() if not orig: canvas.showPage()