def __init__(self, parent=None): super().__init__(parent) self._currentTool = BaseTool() self._mouseDown = False self._glyph = None # drawing attributes self._layerDrawingAttributes = {} self._fallbackDrawingAttributes = dict( showGlyphFill=False, showGlyphStroke=True, showGlyphOnCurvePoints=True, showGlyphStartPoints=True, showGlyphOffCurvePoints=True, showGlyphPointCoordinates=False, showGlyphAnchors=True, showGlyphImage=False, showGlyphMargins=True, showFontVerticalMetrics=True, showFontVerticalMetricsTitles=True, showFontPostscriptBlues=False, showFontPostscriptFamilyBlues=False, ) # cached vertical metrics self._unitsPerEm = 1000 self._descender = -250 self._capHeight = 750 self._ascender = 750 # drawing data cache self._drawingRect = None self._scale = 1.0 self._inverseScale = 0.1 self._impliedPointSize = 1000 # drawing calculation self._centerVertically = True self._centerHorizontally = True self._noPointSizePadding = 200 self._verticalCenterYBuffer = 0 # insert scrollArea self.setFocusPolicy(Qt.ClickFocus) self._scrollArea = QScrollArea(parent) self._scrollArea.resizeEvent = self.resizeEvent self._scrollArea.setWidget(self)
class GlyphView(QWidget): def __init__(self, parent=None): super().__init__(parent) self._currentTool = BaseTool() self._glyph = None # drawing attributes self._layerDrawingAttributes = {} self._fallbackDrawingAttributes = dict( showGlyphFill=False, showGlyphStroke=True, showGlyphOnCurvePoints=True, showGlyphStartPoints=True, showGlyphOffCurvePoints=True, showGlyphPointCoordinates=False, showGlyphAnchors=True, showGlyphImage=False, showGlyphMargins=True, showFontVerticalMetrics=True, showFontVerticalMetricsTitles=True, showFontPostscriptBlues=False, showFontPostscriptFamilyBlues=False, ) # cached vertical metrics self._unitsPerEm = 1000 self._descender = -250 self._capHeight = 750 self._ascender = 750 # drawing data cache self._drawingRect = None self._scale = 1.0 self._inverseScale = 0.1 self._impliedPointSize = 1000 # drawing calculation self._centerVertically = True self._centerHorizontally = True self._noPointSizePadding = 200 self._verticalCenterYBuffer = 0 # insert scrollArea self.setFocusPolicy(Qt.ClickFocus) self._scrollArea = QScrollArea(parent) self._scrollArea.resizeEvent = self.resizeEvent self._scrollArea.setWidget(self) # -------------- # Custom Methods # -------------- def _getGlyphWidthHeight(self): if self._glyph is None: return 0, 0 bottom = self._descender top = max(self._capHeight, self._ascender, self._unitsPerEm + self._descender) width = self._glyph.width height = -bottom + top return width, height def fitScaleMetrics(self): fitHeight = self._scrollArea.viewport().height() _, glyphHeight = self._getGlyphWidthHeight() glyphHeight += self._noPointSizePadding * 2 self.setScale(fitHeight / glyphHeight) def fitScaleBBox(self): if self._glyph is None: return if self._glyph.bounds is None: self.fitScaleMetrics() return _, bottom, _, top = self._glyph.bounds fitHeight = self._scrollArea.viewport().height() glyphHeight = top - bottom glyphHeight += self._noPointSizePadding * 2 self.setScale(fitHeight / glyphHeight) def inverseScale(self): return self._inverseScale def scale(self): return self._scale def setScale(self, scale): self._scale = scale if self._scale <= 0: self._scale = .01 self._inverseScale = 1.0 / self._scale self._impliedPointSize = self._unitsPerEm * self._scale def glyph(self): return self._glyph def setGlyph(self, glyph): self._glyph = glyph self._font = None if glyph is not None: font = self._font = glyph.font if font is not None: self._unitsPerEm = font.info.unitsPerEm if self._unitsPerEm is None: self._unitsPerEm = 1000 self._descender = font.info.descender if self._descender is None: self._descender = -250 self._ascender = font.info.ascender if self._ascender is None: self._ascender = self._unitsPerEm + self._descender self._capHeight = font.info.capHeight if self._capHeight is None: self._capHeight = self._ascender self.setScale(self._scale) self.adjustSize() self._currentTool.toolActivated() self.update() # -------------------- # Notification Support # -------------------- def glyphChanged(self): self.update() def fontInfoChanged(self): self.setGlyph(self._glyph) # --------------- # Display Control # --------------- def drawingAttribute(self, attr, layerName): if layerName is None: return self._fallbackDrawingAttributes.get(attr) d = self._layerDrawingAttributes.get(layerName, {}) return d.get(attr) def setDrawingAttribute(self, attr, value, layerName): if layerName is None: self._fallbackDrawingAttributes[attr] = value else: if layerName not in self._layerDrawingAttributes: self._layerDrawingAttributes[layerName] = {} self._layerDrawingAttributes[layerName][attr] = value self.update() def showFill(self): return self.drawingAttribute("showGlyphFill", None) def setShowFill(self, value): self.setDrawingAttribute("showGlyphFill", value, None) def showStroke(self): return self.drawingAttribute("showGlyphStroke", None) def setShowStroke(self, value): self.setDrawingAttribute("showGlyphStroke", value, None) def showMetrics(self): return self.drawingAttribute("showGlyphMargins", None) def setShowMetrics(self, value): self.setDrawingAttribute("showGlyphMargins", value, None) self.setDrawingAttribute("showFontVerticalMetrics", value, None) def showImage(self): return self.drawingAttribute("showGlyphImage", None) def setShowImage(self, value): self.setDrawingAttribute("showGlyphImage", value, None) def showMetricsTitles(self): return self.drawingAttribute("showFontVerticalMetricsTitles", None) def setShowMetricsTitles(self, value): self.setDrawingAttribute("showFontVerticalMetricsTitles", value, None) def showOnCurvePoints(self): return self.drawingAttribute("showGlyphOnCurvePoints", None) def setShowOnCurvePoints(self, value): self.setDrawingAttribute("showGlyphStartPoints", value, None) self.setDrawingAttribute("showGlyphOnCurvePoints", value, None) def showOffCurvePoints(self): return self.drawingAttribute("showGlyphOffCurvePoints", None) def setShowOffCurvePoints(self, value): self.setDrawingAttribute("showGlyphOffCurvePoints", value, None) def showPointCoordinates(self): return self.drawingAttribute("showGlyphPointCoordinates", None) def setShowPointCoordinates(self, value): self.setDrawingAttribute("showGlyphPointCoordinates", value, None) def showAnchors(self): return self.drawingAttribute("showGlyphAnchors", None) def setShowAnchors(self, value): self.setDrawingAttribute("showGlyphAnchors", value, None) def showBlues(self): return self.drawingAttribute("showFontPostscriptBlues", None) def setShowBlues(self, value): self.setDrawingAttribute("showFontPostscriptBlues", value, None) def showFamilyBlues(self): return self.drawingAttribute("showFontPostscriptFamilyBlues", None) def setShowFamilyBlues(self, value): self.setDrawingAttribute("showFontPostscriptFamilyBlues", value, None) # --------------- # Drawing helpers # --------------- def drawImage(self, painter, glyph, layerName): drawing.drawGlyphImage(painter, glyph, self._inverseScale, self._drawingRect) def drawBlues(self, painter, glyph, layerName): drawing.drawFontPostscriptBlues(painter, glyph, self._inverseScale, self._drawingRect) def drawFamilyBlues(self, painter, glyph, layerName): drawing.drawFontPostscriptFamilyBlues(painter, glyph, self._inverseScale, self._drawingRect) def drawVerticalMetrics(self, painter, glyph, layerName): drawText = self._impliedPointSize > 175 drawing.drawFontVerticalMetrics(painter, glyph, self._inverseScale, self._drawingRect, drawText=drawText) def drawMargins(self, painter, glyph, layerName): drawing.drawGlyphMargins(painter, glyph, self._inverseScale, self._drawingRect) def drawFillAndStroke(self, painter, glyph, layerName): partialAliasing = self._impliedPointSize > 175 showFill = self.drawingAttribute("showGlyphFill", layerName) showStroke = self.drawingAttribute("showGlyphStroke", layerName) drawing.drawGlyphFillAndStroke(painter, glyph, self._inverseScale, self._drawingRect, drawFill=showFill, drawStroke=showStroke, partialAliasing=partialAliasing) def drawPoints(self, painter, glyph, layerName): drawStartPoints = self.drawingAttribute( "showGlyphStartPoints", layerName) and self._impliedPointSize > 175 drawOnCurves = self.drawingAttribute( "showGlyphOnCurvePoints", layerName) and \ self._impliedPointSize > 175 drawOffCurves = self.drawingAttribute( "showGlyphOffCurvePoints", layerName) and \ self._impliedPointSize > 175 drawCoordinates = self.drawingAttribute( "showGlyphPointCoordinates", layerName) and \ self._impliedPointSize > 250 drawing.drawGlyphPoints(painter, glyph, self._inverseScale, self._drawingRect, drawStartPoints=drawStartPoints, drawOnCurves=drawOnCurves, drawOffCurves=drawOffCurves, drawCoordinates=drawCoordinates, backgroundColor=Qt.white) def drawAnchors(self, painter, glyph, layerName): if not self._impliedPointSize > 175: return drawing.drawGlyphAnchors(painter, glyph, self._inverseScale, self._drawingRect) # --------------- # QWidget methods # --------------- def paintEvent(self, event): painter = QPainter(self) painter.setFont(headerFont) painter.setRenderHint(QPainter.Antialiasing) rect = self.rect() # draw the background painter.fillRect(rect, Qt.white) if self._glyph is None: return # apply the overall scale painter.save() # + translate and flip painter.translate(0, self.height()) painter.scale(self._scale, -self._scale) # move into position widgetWidth = self.width() width = self._glyph.width * self._scale diff = widgetWidth - width xOffset = round((diff / 2) * self._inverseScale) yOffset = self._verticalCenterYBuffer * self._inverseScale yOffset -= self._descender painter.translate(xOffset, yOffset) # store the current drawing rect w, h = self.width(), self.height() w *= self._inverseScale h *= self._inverseScale self._drawingRect = (-xOffset, -yOffset, w, h) # gather the layers layerSet = self._glyph.layerSet if layerSet is None: layers = [(self._glyph, None)] else: glyphName = self._glyph.name layers = [] for layerName in reversed(layerSet.layerOrder): layer = layerSet[layerName] if glyphName not in layer: continue glyph = layer[glyphName] if glyph == self._glyph: layerName = None layers.append((glyph, layerName)) for glyph, layerName in layers: # draw the image if self.drawingAttribute("showGlyphImage", layerName): self.drawImage(painter, glyph, layerName) # draw the blues if layerName is None and self.drawingAttribute( "showFontPostscriptBlues", None): self.drawBlues(painter, glyph, layerName) if layerName is None and self.drawingAttribute( "showFontPostscriptFamilyBlues", None): self.drawFamilyBlues(painter, glyph, layerName) # draw the margins if self.drawingAttribute("showGlyphMargins", layerName): self.drawMargins(painter, glyph, layerName) # draw the vertical metrics if layerName is None and self.drawingAttribute( "showFontVerticalMetrics", None): self.drawVerticalMetrics(painter, glyph, layerName) # draw the glyph if self.drawingAttribute("showGlyphFill", layerName) or \ self.drawingAttribute("showGlyphStroke", layerName): # XXX: trying to debug an assertion failure try: self.drawFillAndStroke(painter, glyph, layerName) except AssertionError as e: import traceback print("**********") print("Internal error:", str(e)) print() print(traceback.print_exc()) print() for contour in self._glyph: for point in contour: print(point) print() print("**********") painter.restore() return if self.drawingAttribute("showGlyphOnCurvePoints", layerName) or \ self.drawingAttribute("showGlyphOffCurvePoints", layerName): self.drawPoints(painter, glyph, layerName) if self.drawingAttribute("showGlyphAnchors", layerName): self.drawAnchors(painter, glyph, layerName) self._currentTool.paint(painter) painter.restore() def scrollArea(self): return self._scrollArea def sizeHint(self): viewport = self._scrollArea.viewport() scrollWidth, scrollHeight = viewport.width(), viewport.height() # pick the width and height glyphWidth, glyphHeight = self._getGlyphWidthHeight() glyphWidth = glyphWidth * self._scale glyphHeight = glyphHeight * self._scale xOffset = 1000 * 2 * self._scale yOffset = xOffset width = glyphWidth + xOffset height = glyphHeight + yOffset if scrollWidth > width: width = scrollWidth if scrollHeight > height: height = scrollHeight # calculate and store the vertical centering offset self._verticalCenterYBuffer = (height - glyphHeight) / 2.0 return QSize(width, height) def resizeEvent(self, event): self.adjustSize() event.accept() def showEvent(self, event): # TODO: use fitScaleBBox and adjust scrollBars accordingly self.fitScaleMetrics() self.adjustSize() hSB = self._scrollArea.horizontalScrollBar() vSB = self._scrollArea.verticalScrollBar() hSB.setValue((hSB.minimum() + hSB.maximum()) / 2) vSB.setValue((vSB.minimum() + vSB.maximum()) / 2) def wheelEvent(self, event): if event.modifiers() & Qt.ControlModifier: factor = pow(1.2, event.angleDelta().y() / 120.0) pos = event.pos() # compute new scrollbar position # http://stackoverflow.com/a/32269574/2037879 oldScale = self._scale newScale = self._scale * factor hSB = self._scrollArea.horizontalScrollBar() vSB = self._scrollArea.verticalScrollBar() scrollBarPos = QPointF(hSB.value(), vSB.value()) deltaToPos = (self.mapToParent(pos) - self.pos()) / oldScale delta = deltaToPos * (newScale - oldScale) # TODO: maybe put out a func that does multiply by default self.setScale(newScale) # TODO: maybe merge this in setScale self.adjustSize() self.update() hSB.setValue(scrollBarPos.x() + delta.x()) vSB.setValue(scrollBarPos.y() + delta.y()) event.accept() else: super().wheelEvent(event) # ------------ # Canvas tools # ------------ # current tool @property def currentTool(self): return self._currentTool @currentTool.setter def currentTool(self, tool): self._currentTool = tool self._currentTool.toolActivated() # event directing def mapToCanvas(self, pos): """ Map *pos* from GlyphView widget to canvas coordinates. Note that canvas coordinates are scale-independent while widget coordinates are not. Mouse events sent to tools are in canvas coordinates. """ xOffsetInv, yOffsetInv, _, _ = self._drawingRect x = pos.x() * self._inverseScale + xOffsetInv y = (pos.y() - self.height()) * (-self._inverseScale) + yOffsetInv return QPointF(x, y) def mapToWidget(self, pos): """ Map *pos* from canvas to GlyphView widget coordinates. Note that canvas coordinates are scale-independent while widget coordinates are not. Mouse events sent to tools are in canvas coordinates. """ xOffsetInv, yOffsetInv, _, _ = self._drawingRect x = (pos.x() - xOffsetInv) / self._inverseScale y = (pos.y() - yOffsetInv) / (-self._inverseScale) + self.height() return QPointF(x, y) def _redirectEvent(self, event, callback, transmuteMouseEvent=False): if transmuteMouseEvent: # construct a new event with pos in canvas coordinates canvasPos = self.mapToCanvas(event.localPos()) event = QMouseEvent(event.type(), canvasPos, event.windowPos(), event.screenPos(), event.button(), event.buttons(), event.modifiers()) callback(event) def keyPressEvent(self, event): self._redirectEvent(event, self._currentTool.keyPressEvent) def keyReleaseEvent(self, event): self._redirectEvent(event, self._currentTool.keyReleaseEvent) def mousePressEvent(self, event): self._redirectEvent(event, self._currentTool.mousePressEvent, True) def mouseMoveEvent(self, event): self._redirectEvent(event, self._currentTool.mouseMoveEvent, True) def mouseReleaseEvent(self, event): self._redirectEvent(event, self._currentTool.mouseReleaseEvent, True) def mouseDoubleClickEvent(self, event): self._redirectEvent(event, self._currentTool.mouseDoubleClickEvent, True) # items location def _itemsAt(self, func, obj, justOne=True): """ Go through all anchors, points and components (in this order) in the glyph, construct their canvas path and list items for which *func(path, obj)* returns True, or only return the first item if *justOne* is set to True. An item is a (point, contour) or (anchor, None) or (component, None) tuple. The second argument permits accessing parent contour to post notifications. Here is a sample *func* function that tests whether item with path *path* contains *pos*: def myFunction(path, pos): return path.contains(pos) This is useful to find out whether an item was clicked on canvas. """ scale = self._inverseScale # TODO: export this from drawing or use QSettings. # anchor anchorSize = 6 * scale anchorHalfSize = anchorSize / 2 # offCurve offWidth = 5 * scale offHalf = offWidth / 2.0 # onCurve onWidth = 7 * scale onHalf = onWidth / 2.0 # onCurve smooth smoothWidth = 8 * scale smoothHalf = smoothWidth / 2.0 if not justOne: ret = dict( anchors=[], contours=[], points=[], components=[], ) for anchor in reversed(self._glyph.anchors): path = QPainterPath() path.addEllipse(anchor.x - anchorHalfSize, anchor.y - anchorHalfSize, anchorSize, anchorSize) if func(path, obj): if justOne: return (anchor, None) ret["anchors"].append(anchor) for contour in reversed(self._glyph): for point in contour: path = QPainterPath() if point.segmentType is None: x = point.x - offHalf y = point.y - offHalf path.addEllipse(x, y, offWidth, offWidth) elif point.smooth: x = point.x - smoothHalf y = point.y - smoothHalf path.addEllipse(x, y, smoothWidth, smoothWidth) else: x = point.x - onHalf y = point.y - onHalf path.addRect(x, y, onWidth, onWidth) if func(path, obj): if justOne: return (point, contour) ret["contours"].append(contour) ret["points"].append(point) for component in reversed(self._glyph.components): path = component.getRepresentation("defconQt.QPainterPath") if func(path, obj): if justOne: return (component, None) ret["components"].append(component) if not justOne: return ret return None def itemAt(self, pos): """ Find one item at *pos*. An item is a (point, contour) or (anchor, None) or (component, None) tuple. """ return self.itemsAt(pos, True) def itemsAt(self, pos, items=False): """ Find items at *pos*. """ return self._itemsAt(lambda path, pos: path.contains(pos), pos, items) def items(self, rect): """ Find items that intersect with *rect* (can be any QPainterPath). """ return self._itemsAt(lambda path, rect: path.intersects(rect), rect, False)
class GlyphView(QWidget): def __init__(self, parent=None): super().__init__(parent) self._currentTool = BaseTool() self._mouseDown = False self._glyph = None # drawing attributes self._layerDrawingAttributes = {} self._fallbackDrawingAttributes = dict( showGlyphFill=False, showGlyphStroke=True, showGlyphOnCurvePoints=True, showGlyphStartPoints=True, showGlyphOffCurvePoints=True, showGlyphPointCoordinates=False, showGlyphAnchors=True, showGlyphImage=False, showGlyphMargins=True, showFontVerticalMetrics=True, showFontVerticalMetricsTitles=True, showFontPostscriptBlues=False, showFontPostscriptFamilyBlues=False, ) # cached vertical metrics self._unitsPerEm = 1000 self._descender = -250 self._capHeight = 750 self._ascender = 750 # drawing data cache self._drawingRect = None self._scale = 1.0 self._inverseScale = 0.1 self._impliedPointSize = 1000 # drawing calculation self._centerVertically = True self._centerHorizontally = True self._noPointSizePadding = 200 self._verticalCenterYBuffer = 0 # insert scrollArea self.setFocusPolicy(Qt.ClickFocus) self._scrollArea = QScrollArea(parent) self._scrollArea.resizeEvent = self.resizeEvent self._scrollArea.setWidget(self) # -------------- # Custom Methods # -------------- def _getGlyphWidthHeight(self): if self._glyph is None: return 0, 0 bottom = self._descender top = max(self._capHeight, self._ascender, self._unitsPerEm + self._descender) width = self._glyph.width height = -bottom + top return width, height def fitScaleMetrics(self): fitHeight = self._scrollArea.viewport().height() _, glyphHeight = self._getGlyphWidthHeight() glyphHeight += self._noPointSizePadding * 2 self.setScale(fitHeight / glyphHeight) def fitScaleBBox(self): if self._glyph is None: return if self._glyph.bounds is None: self.fitScaleMetrics() return _, bottom, _, top = self._glyph.bounds fitHeight = self._scrollArea.viewport().height() glyphHeight = top - bottom glyphHeight += self._noPointSizePadding * 2 self.setScale(fitHeight / glyphHeight) def inverseScale(self): return self._inverseScale def scale(self): return self._scale def setScale(self, scale): self._scale = scale if self._scale <= 0: self._scale = .01 self._inverseScale = 1.0 / self._scale self._impliedPointSize = self._unitsPerEm * self._scale def glyph(self): return self._glyph def setGlyph(self, glyph): # TODO: disable/enable there makes sense, right? self._currentTool.toolDisabled() self._glyph = glyph self._font = None if glyph is not None: font = self._font = glyph.font if font is not None: self._unitsPerEm = font.info.unitsPerEm if self._unitsPerEm is None: self._unitsPerEm = 1000 self._descender = font.info.descender if self._descender is None: self._descender = -250 self._ascender = font.info.ascender if self._ascender is None: self._ascender = self._unitsPerEm + self._descender self._capHeight = font.info.capHeight if self._capHeight is None: self._capHeight = self._ascender self.setScale(self._scale) self.adjustSize() self._currentTool.toolActivated() self.update() # -------------------- # Notification Support # -------------------- def glyphChanged(self): self.update() def fontInfoChanged(self): self.setGlyph(self._glyph) # --------------- # Display Control # --------------- def drawingAttribute(self, attr, layerName): if layerName is None: return self._fallbackDrawingAttributes.get(attr) d = self._layerDrawingAttributes.get(layerName, {}) return d.get(attr) def setDrawingAttribute(self, attr, value, layerName): if layerName is None: self._fallbackDrawingAttributes[attr] = value else: if layerName not in self._layerDrawingAttributes: self._layerDrawingAttributes[layerName] = {} self._layerDrawingAttributes[layerName][attr] = value self.update() def showFill(self): return self.drawingAttribute("showGlyphFill", None) def setShowFill(self, value): self.setDrawingAttribute("showGlyphFill", value, None) def showStroke(self): return self.drawingAttribute("showGlyphStroke", None) def setShowStroke(self, value): self.setDrawingAttribute("showGlyphStroke", value, None) def showMetrics(self): return self.drawingAttribute("showGlyphMargins", None) def setShowMetrics(self, value): self.setDrawingAttribute("showGlyphMargins", value, None) self.setDrawingAttribute("showFontVerticalMetrics", value, None) def showImage(self): return self.drawingAttribute("showGlyphImage", None) def setShowImage(self, value): self.setDrawingAttribute("showGlyphImage", value, None) def showMetricsTitles(self): return self.drawingAttribute("showFontVerticalMetricsTitles", None) def setShowMetricsTitles(self, value): self.setDrawingAttribute("showFontVerticalMetricsTitles", value, None) def showOnCurvePoints(self): return self.drawingAttribute("showGlyphOnCurvePoints", None) def setShowOnCurvePoints(self, value): self.setDrawingAttribute("showGlyphStartPoints", value, None) self.setDrawingAttribute("showGlyphOnCurvePoints", value, None) def showOffCurvePoints(self): return self.drawingAttribute("showGlyphOffCurvePoints", None) def setShowOffCurvePoints(self, value): self.setDrawingAttribute("showGlyphOffCurvePoints", value, None) def showPointCoordinates(self): return self.drawingAttribute("showGlyphPointCoordinates", None) def setShowPointCoordinates(self, value): self.setDrawingAttribute("showGlyphPointCoordinates", value, None) def showAnchors(self): return self.drawingAttribute("showGlyphAnchors", None) def setShowAnchors(self, value): self.setDrawingAttribute("showGlyphAnchors", value, None) def showBlues(self): return self.drawingAttribute("showFontPostscriptBlues", None) def setShowBlues(self, value): self.setDrawingAttribute("showFontPostscriptBlues", value, None) def showFamilyBlues(self): return self.drawingAttribute("showFontPostscriptFamilyBlues", None) def setShowFamilyBlues(self, value): self.setDrawingAttribute("showFontPostscriptFamilyBlues", value, None) # --------------- # Drawing helpers # --------------- def drawImage(self, painter, glyph, layerName): drawing.drawGlyphImage( painter, glyph, self._inverseScale, self._drawingRect) def drawBlues(self, painter, glyph, layerName): drawing.drawFontPostscriptBlues( painter, glyph, self._inverseScale, self._drawingRect) def drawFamilyBlues(self, painter, glyph, layerName): drawing.drawFontPostscriptFamilyBlues( painter, glyph, self._inverseScale, self._drawingRect) def drawVerticalMetrics(self, painter, glyph, layerName): drawText = self._impliedPointSize > 175 drawing.drawFontVerticalMetrics( painter, glyph, self._inverseScale, self._drawingRect, drawText=drawText) def drawMargins(self, painter, glyph, layerName): drawing.drawGlyphMargins( painter, glyph, self._inverseScale, self._drawingRect) def drawFillAndStroke(self, painter, glyph, layerName): partialAliasing = self._impliedPointSize > 175 showFill = self.drawingAttribute("showGlyphFill", layerName) showStroke = self.drawingAttribute("showGlyphStroke", layerName) drawing.drawGlyphFillAndStroke( painter, glyph, self._inverseScale, self._drawingRect, drawFill=showFill, drawStroke=showStroke, partialAliasing=partialAliasing) def drawPoints(self, painter, glyph, layerName): drawStartPoints = self.drawingAttribute( "showGlyphStartPoints", layerName) and self._impliedPointSize > 175 drawOnCurves = self.drawingAttribute( "showGlyphOnCurvePoints", layerName) and \ self._impliedPointSize > 175 drawOffCurves = self.drawingAttribute( "showGlyphOffCurvePoints", layerName) and \ self._impliedPointSize > 175 drawCoordinates = self.drawingAttribute( "showGlyphPointCoordinates", layerName) and \ self._impliedPointSize > 250 drawing.drawGlyphPoints( painter, glyph, self._inverseScale, self._drawingRect, drawStartPoints=drawStartPoints, drawOnCurves=drawOnCurves, drawOffCurves=drawOffCurves, drawCoordinates=drawCoordinates, backgroundColor=Qt.white) def drawAnchors(self, painter, glyph, layerName): if not self._impliedPointSize > 175: return drawing.drawGlyphAnchors( painter, glyph, self._inverseScale, self._drawingRect) # --------------- # QWidget methods # --------------- def paintEvent(self, event): painter = QPainter(self) painter.setFont(headerFont) painter.setRenderHint(QPainter.Antialiasing) rect = self.rect() # draw the background painter.fillRect(rect, Qt.white) if self._glyph is None: return # apply the overall scale painter.save() # + translate and flip painter.translate(0, self.height()) painter.scale(self._scale, -self._scale) # move into position widgetWidth = self.width() width = self._glyph.width * self._scale diff = widgetWidth - width xOffset = round((diff / 2) * self._inverseScale) yOffset = self._verticalCenterYBuffer * self._inverseScale yOffset -= self._descender painter.translate(xOffset, yOffset) # store the current drawing rect w, h = self.width(), self.height() w *= self._inverseScale h *= self._inverseScale self._drawingRect = (-xOffset, -yOffset, w, h) # gather the layers layerSet = self._glyph.layerSet if layerSet is None: layers = [(self._glyph, None)] else: glyphName = self._glyph.name layers = [] for layerName in reversed(layerSet.layerOrder): layer = layerSet[layerName] if glyphName not in layer: continue glyph = layer[glyphName] if glyph == self._glyph: layerName = None layers.append((glyph, layerName)) for glyph, layerName in layers: # draw the image if self.drawingAttribute("showGlyphImage", layerName): self.drawImage(painter, glyph, layerName) # draw the blues if layerName is None and self.drawingAttribute( "showFontPostscriptBlues", None): self.drawBlues(painter, glyph, layerName) if layerName is None and self.drawingAttribute( "showFontPostscriptFamilyBlues", None): self.drawFamilyBlues(painter, glyph, layerName) # draw the margins if self.drawingAttribute("showGlyphMargins", layerName): self.drawMargins(painter, glyph, layerName) # draw the vertical metrics if layerName is None and self.drawingAttribute( "showFontVerticalMetrics", None): self.drawVerticalMetrics(painter, glyph, layerName) # draw the glyph if self.drawingAttribute("showGlyphFill", layerName) or \ self.drawingAttribute("showGlyphStroke", layerName): self.drawFillAndStroke(painter, glyph, layerName) if self.drawingAttribute("showGlyphOnCurvePoints", layerName) or \ self.drawingAttribute("showGlyphOffCurvePoints", layerName): self.drawPoints(painter, glyph, layerName) if self.drawingAttribute("showGlyphAnchors", layerName): self.drawAnchors(painter, glyph, layerName) self._currentTool.paint(painter) painter.restore() def scrollArea(self): return self._scrollArea def sizeHint(self): viewport = self._scrollArea.viewport() scrollWidth, scrollHeight = viewport.width(), viewport.height() # pick the width and height glyphWidth, glyphHeight = self._getGlyphWidthHeight() glyphWidth = glyphWidth * self._scale glyphHeight = glyphHeight * self._scale xOffset = 1000 * 2 * self._scale yOffset = xOffset width = glyphWidth + xOffset height = glyphHeight + yOffset if scrollWidth > width: width = scrollWidth if scrollHeight > height: height = scrollHeight # calculate and store the vertical centering offset self._verticalCenterYBuffer = (height - glyphHeight) / 2.0 return QSize(width, height) def resizeEvent(self, event): self.adjustSize() event.accept() def showEvent(self, event): # TODO: use fitScaleBBox and adjust scrollBars accordingly self.fitScaleMetrics() self.adjustSize() hSB = self._scrollArea.horizontalScrollBar() vSB = self._scrollArea.verticalScrollBar() hSB.setValue((hSB.minimum() + hSB.maximum()) / 2) vSB.setValue((vSB.minimum() + vSB.maximum()) / 2) def wheelEvent(self, event): if event.modifiers() & Qt.ControlModifier: factor = pow(1.2, event.angleDelta().y() / 120.0) pos = event.pos() # compute new scrollbar position # http://stackoverflow.com/a/32269574/2037879 oldScale = self._scale newScale = self._scale * factor hSB = self._scrollArea.horizontalScrollBar() vSB = self._scrollArea.verticalScrollBar() scrollBarPos = QPointF(hSB.value(), vSB.value()) deltaToPos = (self.mapToParent(pos) - self.pos()) / oldScale delta = deltaToPos * (newScale - oldScale) # TODO: maybe put out a func that does multiply by default self.setScale(newScale) # TODO: maybe merge this in setScale self.adjustSize() self.update() hSB.setValue(scrollBarPos.x() + delta.x()) vSB.setValue(scrollBarPos.y() + delta.y()) event.accept() else: super().wheelEvent(event) # ------------ # Canvas tools # ------------ # current tool def currentTool(self): return self._currentTool def setCurrentTool(self, tool): if self._mouseDown: return False self._currentTool.toolDisabled() self._currentTool = tool self._currentTool.toolActivated() return True # event directing def mapToCanvas(self, pos): """ Map *pos* from GlyphView widget to canvas coordinates. Note that canvas coordinates are scale-independent while widget coordinates are not. Mouse events sent to tools are in canvas coordinates. """ xOffsetInv, yOffsetInv, _, _ = self._drawingRect x = pos.x() * self._inverseScale + xOffsetInv y = (pos.y() - self.height()) * (- self._inverseScale) + yOffsetInv return QPointF(x, y) def mapToWidget(self, pos): """ Map *pos* from canvas to GlyphView widget coordinates. Note that canvas coordinates are scale-independent while widget coordinates are not. Mouse events sent to tools are in canvas coordinates. """ xOffsetInv, yOffsetInv, _, _ = self._drawingRect x = (pos.x() - xOffsetInv) / self._inverseScale y = (pos.y() - yOffsetInv) / (- self._inverseScale) + self.height() return QPointF(x, y) def _redirectEvent(self, event, callback, transmuteMouseEvent=False): if transmuteMouseEvent: # construct a new event with pos in canvas coordinates canvasPos = self.mapToCanvas(event.localPos()) event = QMouseEvent( event.type(), canvasPos, event.windowPos(), event.screenPos(), event.button(), event.buttons(), event.modifiers() ) callback(event) def keyPressEvent(self, event): self._redirectEvent(event, self._currentTool.keyPressEvent) def keyReleaseEvent(self, event): self._redirectEvent(event, self._currentTool.keyReleaseEvent) def mousePressEvent(self, event): self._mouseDown = True self._redirectEvent(event, self._currentTool.mousePressEvent, True) def mouseMoveEvent(self, event): self._redirectEvent(event, self._currentTool.mouseMoveEvent, True) def mouseReleaseEvent(self, event): self._redirectEvent(event, self._currentTool.mouseReleaseEvent, True) self._mouseDown = False def mouseDoubleClickEvent(self, event): self._redirectEvent( event, self._currentTool.mouseDoubleClickEvent, True) # items location def _itemsAt(self, func, obj, justOne=True): """ Go through all anchors, points and components (in this order) in the glyph, construct their canvas path and list items for which *func(path, obj)* returns True, or only return the first item if *justOne* is set to True. An item is a (point, contour) or (anchor, None) or (component, None) tuple. The second argument permits accessing parent contour to post notifications. Here is a sample *func* function that tests whether item with path *path* contains *pos*: def myFunction(path, pos): return path.contains(pos) This is useful to find out whether an item was clicked on canvas. """ scale = self._inverseScale # TODO: export this from drawing or use QSettings. # anchor anchorSize = 6 * scale anchorHalfSize = anchorSize / 2 # offCurve offWidth = 5 * scale offHalf = offWidth / 2.0 # onCurve onWidth = 7 * scale onHalf = onWidth / 2.0 # onCurve smooth smoothWidth = 8 * scale smoothHalf = smoothWidth / 2.0 if not justOne: ret = dict( anchors=[], contours=[], points=[], components=[], ) for anchor in reversed(self._glyph.anchors): path = QPainterPath() path.addEllipse(anchor.x - anchorHalfSize, anchor.y - anchorHalfSize, anchorSize, anchorSize) if func(path, obj): if justOne: return (anchor, None) ret["anchors"].append(anchor) for contour in reversed(self._glyph): for point in contour: path = QPainterPath() if point.segmentType is None: x = point.x - offHalf y = point.y - offHalf path.addEllipse(x, y, offWidth, offWidth) elif point.smooth: x = point.x - smoothHalf y = point.y - smoothHalf path.addEllipse(x, y, smoothWidth, smoothWidth) else: x = point.x - onHalf y = point.y - onHalf path.addRect(x, y, onWidth, onWidth) if func(path, obj): if justOne: return (point, contour) ret["contours"].append(contour) ret["points"].append(point) for component in reversed(self._glyph.components): path = component.getRepresentation("defconQt.QPainterPath") if func(path, obj): if justOne: return (component, None) ret["components"].append(component) if not justOne: return ret return None def itemAt(self, pos): """ Find one item at *pos*. An item is a (point, contour) or (anchor, None) or (component, None) tuple. """ return self.itemsAt(pos, True) def itemsAt(self, pos, items=False): """ Find items at *pos*. """ return self._itemsAt(lambda path, pos: path.contains(pos), pos, items) def items(self, rect): """ Find items that intersect with *rect* (can be any QPainterPath). """ return self._itemsAt( lambda path, rect: path.intersects(rect), rect, False)