diff options
| author | Adrien Tétar | 2015-09-21 22:14:45 +0200 |
|---|---|---|
| committer | Adrien Tétar | 2015-09-21 22:14:45 +0200 |
| commit | ce68fd8b05271c317d908ce4c8d73c9cbd1656ad (patch) | |
| tree | 42493efb33c2a1841203a82c92e22b893bb0b8e4 /Lib | |
| parent | 411b0f594f6d660f13f3e1b57873a9fbda0dbd89 (diff) | |
| download | trufont-ce68fd8b05271c317d908ce4c8d73c9cbd1656ad.tar.bz2 | |
meta/defcon: check-in copy-paste, template glyph
Diffstat (limited to 'Lib')
| -rw-r--r-- | Lib/defconQt/__main__.py | 4 | ||||
| -rw-r--r-- | Lib/defconQt/fontView.py | 10 | ||||
| -rw-r--r-- | Lib/defconQt/glyphView.py | 58 | ||||
| -rw-r--r-- | Lib/defconQt/objects/defcon.py | 162 | ||||
| -rw-r--r-- | Lib/defconQt/pens/__init__.py | 0 | ||||
| -rw-r--r-- | Lib/defconQt/pens/copySelectionPen.py | 59 |
6 files changed, 271 insertions, 22 deletions
diff --git a/Lib/defconQt/__main__.py b/Lib/defconQt/__main__.py index 07f273c..c1e548c 100644 --- a/Lib/defconQt/__main__.py +++ b/Lib/defconQt/__main__.py @@ -1,4 +1,4 @@ -from defcon import Font +from defconQt.objects.defcon import TFont from defconQt.fontView import MainWindow import sys @@ -20,6 +20,6 @@ representationFactories.registerAllFactories() app = QApplication(sys.argv) # TODO: http://stackoverflow.com/a/21330349/2037879 app.setWindowIcon(QIcon("defconQt/resources/icon.png")) -window = MainWindow(Font(ufoFile)) +window = MainWindow(TFont(ufoFile)) window.show() sys.exit(app.exec_()) diff --git a/Lib/defconQt/fontView.py b/Lib/defconQt/fontView.py index ba773c3..6a603a4 100644 --- a/Lib/defconQt/fontView.py +++ b/Lib/defconQt/fontView.py @@ -1,10 +1,9 @@ -from defcon import Font from defconQt.featureTextEditor import MainEditWindow from defconQt.fontInfo import TabDialog from defconQt.glyphCollectionView import GlyphCollectionWidget from defconQt.glyphView import MainGfxWindow from defconQt.groupsView import GroupsWindow -from defconQt.objects.defcon import CharacterSet +from defconQt.objects.defcon import CharacterSet, TFont from defconQt.spaceCenter import MainSpaceWindow from fontTools.agl import AGL2UV # TODO: remove globs when things start to stabilize @@ -80,7 +79,7 @@ class AddGlyphDialog(QDialog): self.setLayout(layout) @staticmethod - def getNewGlyphNames(parent=None, currentGlyphs=None): + def getNewGlyphNames(parent, currentGlyphs=None): dialog = AddGlyphDialog(currentGlyphs, parent) result = dialog.exec_() sortFont = False @@ -229,7 +228,7 @@ class SortDialog(QDialog): return 0 @staticmethod - def getDescriptor(parent=None, sortDescriptor=None): + def getDescriptor(parent, sortDescriptor=None): dialog = SortDialog(sortDescriptor, parent) result = dialog.exec_() if dialog.characterSetBox.isChecked(): @@ -336,7 +335,7 @@ class MainWindow(QMainWindow): def newFile(self): ok = self.maybeSaveBeforeExit() if not ok: return - self.font = Font() + self.font = TFont() self.font.info.unitsPerEm = 1000 self.font.info.ascender = 750 self.font.info.descender = -250 @@ -476,6 +475,7 @@ class MainWindow(QMainWindow): else: glyph.lib["public.markColor"] = ",".join(str(c) for c in color.getRgbF()) + # TODO: maybe store this in TFont def newStandardGlyph(self, name): self.font.newGlyph(name) self.font[name].width = 500 diff --git a/Lib/defconQt/glyphView.py b/Lib/defconQt/glyphView.py index c42e4b7..cfeff3b 100644 --- a/Lib/defconQt/glyphView.py +++ b/Lib/defconQt/glyphView.py @@ -1,7 +1,7 @@ from enum import Enum from math import copysign -from defcon.objects.contour import Contour -from defcon.objects.point import Point +from defconQt.objects.defcon import TContour, TGlyph +from defconQt.pens.copySelectionPen import CopySelectionPen from fontTools.misc import bezierTools from PyQt5.QtCore import *#QFile, QLineF, QObject, QPointF, QRectF, QSize, Qt from PyQt5.QtGui import *#QBrush, QColor, QImage, QKeySequence, QPainter, QPainterPath, QPixmap, QPen @@ -11,13 +11,13 @@ from PyQt5.QtWidgets import *#(QAction, QActionGroup, QApplication, QFileDialog, from PyQt5.QtOpenGL import QGL, QGLFormat, QGLWidget -class GotoWindow(QDialog): +class GotoDialog(QDialog): alphabetical = [ dict(type="alphabetical", allowPseudoUnicode=True) ] def __init__(self, font, parent=None): - super(GotoWindow, self).__init__(parent) + super(GotoDialog, self).__init__(parent) self.setWindowModality(Qt.WindowModal) self.setWindowTitle("Go to…") self.font = font @@ -79,14 +79,18 @@ class GotoWindow(QDialog): self.glyphList.addItems(glyphs) if select: self.glyphList.setCurrentRow(0) - def accept(self): - # TODO: zap going thru the view, here and above - currentItem = self.glyphList.currentItem() + @staticmethod + def getNewGlyph(parent, font): + dialog = GotoDialog(font, parent) + result = dialog.exec_() + currentItem = dialog.glyphList.currentItem() + newGlyph = None if currentItem is not None: - targetGlyph = currentItem.text() - if not targetGlyph in self.font: return - self._view.setGlyph(self.font[targetGlyph]) - super(GotoWindow, self).accept() + newGlyphName = currentItem.text() + if newGlyphName in dialog.font: + newGlyph = dialog.font[newGlyphName] + return (newGlyph, result) + class MainGfxWindow(QMainWindow): def __init__(self, glyph, parent=None): @@ -263,6 +267,9 @@ class OffCurvePointItem(QGraphicsEllipseItem): value.setY(0) elif change == QGraphicsItem.ItemPositionHasChanged: self.parentItem()._CPMoved(value) + # TODO: consider what to do w offCurves + #elif change == QGraphicsItem.ItemSelectedHasChanged: + # pass#self.parentItem()._CPSelChanged(value) return value def mousePressEvent(self, event): @@ -432,6 +439,8 @@ class OnCurvePointItem(QGraphicsPathItem): self._contour[pointIndex+1].x = self.pos().x()+nextPos.x() self._contour[pointIndex+1].y = self.pos().y()+nextPos.y() self.setShallowDirty() + elif change == QGraphicsItem.ItemSelectedHasChanged: + self._point.selected = value return value def setShallowDirty(self): @@ -667,9 +676,11 @@ class GlyphScene(QGraphicsScene): event.accept() return elif key == Qt.Key_J: - glyph = self.views()[0]._glyph - dialog = GotoWindow(glyph.getParent(), self.parent()) - dialog.exec_() + view = self.views()[0] + glyph = view._glyph + newGlyph, ok = GotoDialog.getNewGlyph(self.parent(), glyph.getParent()) + if ok and newGlyph is not None: + view.setGlyph(newGlyph) return elif event.matches(QKeySequence.Undo): if len(self._dataForUndo) > 0: @@ -699,6 +710,23 @@ class GlyphScene(QGraphicsScene): self.setSelectionArea(QPainterPath(), view.transform()) event.accept() return + elif event.matches(QKeySequence.Copy): + clipboard = QApplication.clipboard() + mimeData = clipboard.mimeData() + pen = CopySelectionPen() + self._glyphObject.drawPoints(pen) + # XXX: clipboard should outlive the widget window! + self._clipboardObject = pen.getGlyph().serializeForUndo() + event.accept() + return + elif event.matches(QKeySequence.Paste): + if self._clipboardObject is None: return + pen = self._glyphObject.getPointPen() + pasteGlyph = TGlyph() + pasteGlyph.deserializeFromUndo(self._clipboardObject) + pasteGlyph.drawPoints(pen) + event.accept() + return else: sel = self.selectedItems() if len(sel) == 1 and isinstance(sel[0], OffCurvePointItem) and \ @@ -798,7 +826,7 @@ class GlyphScene(QGraphicsScene): lastContour.dirty = True self._editing = True elif not (touched and isinstance(touched, OnCurvePointItem)): - nextC = Contour() + nextC = TContour() self._glyphObject.appendContour(nextC) nextC.addPoint((x,y), "move") diff --git a/Lib/defconQt/objects/defcon.py b/Lib/defconQt/objects/defcon.py index 487865a..5de0297 100644 --- a/Lib/defconQt/objects/defcon.py +++ b/Lib/defconQt/objects/defcon.py @@ -1,3 +1,165 @@ +from defcon import Font, Contour, Glyph, Point +from defcon.objects.base import BaseObject + +class TFont(Font): + def __init__(self, *args, **kwargs): + if not "glyphClass" in kwargs: + kwargs["glyphClass"] = TGlyph + if not "glyphContourClass" in kwargs: + kwargs["glyphContourClass"] = TContour + if not "glyphPointClass" in kwargs: + kwargs["glyphPointClass"] = TPoint + super(TFont, self).__init__(*args, **kwargs) + + # XXX: had to copy all of this so as to exclude template glyphs from + # save. It pains me that's the only thing to be done w current upstream + def save(self, path=None, formatVersion=None): + saveAs = False + if path is not None and path != self._path: + saveAs = True + else: + path = self._path + ## work out the format version + # if None is given, fallback to the one that + # came in when the UFO was loaded + if formatVersion is None and self._ufoFormatVersion is not None: + formatVersion = self._ufoFormatVersion + # otherwise fallback to 2 + elif self._ufoFormatVersion is None: + formatVersion = 2 + ## make a UFOWriter + ufoWriter = ufoLib.UFOWriter(path, formatVersion=formatVersion) + ## save objects + saveInfo = False + saveKerning = False + saveGroups = False + saveFeatures = False + ## lib should always be saved + saveLib = True + # if in a save as, save all objects + if saveAs: + saveInfo = True + saveKerning = True + saveGroups = True + saveFeatures = True + ## if changing ufo format versions, save all objects + if self._ufoFormatVersion != formatVersion: + saveInfo = True + saveKerning = True + saveGroups = True + saveFeatures = True + # save info, kerning and features if they are dirty + if self._info is not None and self._info.dirty: + saveInfo = True + if self._kerning is not None and self._kerning.dirty: + saveKerning = True + if self._features is not None and self._features.dirty: + saveFeatures = True + # always save groups and lib if they are loaded + # as they contain sub-objects that may not notify + # the main object about changes. + if self._groups is not None: + saveGroups = True + if self._lib is not None: + saveLib = True + # save objects as needed + if saveInfo: + ufoWriter.writeInfo(self.info) + self._stampInfoDataState() + self.info.dirty = False + if saveKerning: + ufoWriter.writeKerning(self.kerning) + self._stampKerningDataState() + self.kerning.dirty = False + if saveGroups: + ufoWriter.writeGroups(self.groups) + self._stampGroupsDataState() + if saveFeatures and formatVersion > 1: + ufoWriter.writeFeatures(self.features.text) + self._stampFeaturesDataState() + if saveLib: + # if making format version 1, do some + # temporary down conversion before + # passing the lib to the writer + libCopy = dict(self.lib) + if formatVersion == 1: + self._convertToFormatVersion1RoboFabData(libCopy) + ufoWriter.writeLib(libCopy) + self._stampLibDataState() + ## save glyphs + # for a save as operation, load all the glyphs + # and mark them as dirty. + if saveAs: + for glyph in self: + glyph.dirty = True + glyphSet = ufoWriter.getGlyphSet() + for glyphName, glyphObject in self._glyphs.items(): + if glyphObject.template: continue + if glyphObject.dirty: + glyphSet.writeGlyph(glyphName, glyphObject, glyphObject.drawPoints) + self._stampGlyphDataState(glyphObject) + # remove deleted glyphs + if not saveAs and self._scheduledForDeletion: + for glyphName in self._scheduledForDeletion: + if glyphName in glyphSet: + glyphSet.deleteGlyph(glyphName) + glyphSet.writeContents() + self._glyphSet = glyphSet + self._scheduledForDeletion = [] + self._path = path + self._ufoFormatVersion = formatVersion + self.dirty = False + +class TGlyph(Glyph): + def __init__(self, *args, **kwargs): + super(TGlyph, self).__init__(*args, **kwargs) + self._template = False + + def _get_template(self): + return self._template + + def _set_template(self, value): + self._template = value + + template = property(_get_template, _set_template, doc="A boolean indicating whether the glyph is a template glyph.") + + def _set_dirty(self, value): + BaseObject._set_dirty(self, value) + if value: + self.template = False + + dirty = property(BaseObject._get_dirty, _set_dirty) + +class TContour(Contour): + def __init__(self, pointClass=None): + if pointClass is None: + pointClass = TPoint + super(TContour, self).__init__(pointClass) + + def drawPoints(self, pointPen): + """ + Draw the contour with **pointPen**. + """ + pointPen.beginPath() + for point in self._points: + pointPen.addPoint((point.x, point.y), segmentType=point.segmentType, smooth=point.smooth, name=point.name, selected=point.selected) + pointPen.endPath() + +class TPoint(Point): + __slots__ = ["_selected"] + + def __init__(self, pt, segmentType=None, smooth=False, name=None, selected=False): + super(TPoint, self).__init__(pt, segmentType, smooth, name) + self._selected = selected + + def _get_selected(self): + return self._selected + + def _set_selected(self, value): + self._selected = value + + selected = property(_get_selected, _set_selected, doc="A boolean indicating the selected state of the point.") + class CharacterSet(object): __slots__ = ["_name", "_glyphNames"] diff --git a/Lib/defconQt/pens/__init__.py b/Lib/defconQt/pens/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Lib/defconQt/pens/__init__.py diff --git a/Lib/defconQt/pens/copySelectionPen.py b/Lib/defconQt/pens/copySelectionPen.py new file mode 100644 index 0000000..53d917b --- /dev/null +++ b/Lib/defconQt/pens/copySelectionPen.py @@ -0,0 +1,59 @@ +from defcon import Glyph +from robofab.pens.pointPen import AbstractPointPen + +class CopySelectionPen(AbstractPointPen): + def __init__(self, glyph=None): + if glyph is None: + glyph = Glyph() + self._glyph = glyph + self._contour = None + self._havePoint = False + self._originalContourIsOpen = False + + def beginPath(self): + self._contour = self._glyph.contourClass(pointClass=self._glyph.pointClass) + + def endPath(self, keepOpened=False): + if self._havePoint: + self.elideOrphanOffCurves(True) + if self._originalContourIsOpen or keepOpened: + self._contour[0].segmentType = "move" + self._contour.dirty = False + self._glyph.appendContour(self._contour) + self._contour = None + self._havePoint = False + self._originalContourIsOpen = False + + def addPoint(self, pt, segmentType=None, smooth=False, name=None, selected=False, **kwargs): + if segmentType == "move": + self._originalContourIsOpen = True + if segmentType is None or selected: + if selected: + self._havePoint = True + self._contour.addPoint(pt, segmentType, smooth, name) + else: + self.elideOrphanOffCurves(False) + if self._havePoint: + # We started drawing a path and we have a gap in it. Create + # a new contour (and leave this one opened). + self.endPath(True) + self.beginPath() + + def addComponent(self, baseGlyphName, transformation): + pass # XXX + + def elideOrphanOffCurves(self, arrivedAtBoundary): + # onCurves that aren't selected and preceding offCurves if any are + # elided + for _ in range(2): + if len(self._contour): + if len(self._contour) > 1 and arrivedAtBoundary and \ + self._contour[0].segmentType == "curve": + # We're at the end of drawing and the offCurves lead to begin + # onCurve. Let them in. + pass + elif self._contour[-1].segmentType is None: + self._contour.removePoint(self._contour[-1]) + + def getGlyph(self): + return self._glyph |
