Δύο γόμες και γιατί η διαγραφή ανήκει στην πινελιά
Μια πινελιά γόμας ως ξεχωριστό στοιχείο είναι ο προφανής σχεδιασμός. Η μετακίνηση αντικειμένων τον χαλά, οπότε στο Lulucat Notes η διαγραφή ανήκει στην πινελιά από την οποία αφαιρεί μελάνι.
Όταν σβήνουμε σε ένα σημειωματάριο, μπορεί να εννοούμε δύο διαφορετικά πράγματα. Μερικές φορές ένα σημάδι είναι λάθος και πρέπει να φύγει ολόκληρο: μια βοηθητική γραμμή, μια διαγραμμένη λέξη, μια τυχαία πινελιά από μια παλάμη που ακουμπά. Μερικές φορές λάθος είναι μόνο ένα τμήμα ενός σημαδιού: μισός χαρακτήρας, ένας κλάδος ενός διαγράμματος, η ουρά μιας πινελιάς μαρκαδόρου επισήμανσης που πέρασε τη φράση. Το Lulucat Notes προσφέρει πλέον και τα δύο, ως δύο ξεχωριστά εργαλεία.
Το δυσκολότερο σχεδιαστικό ερώτημα είναι πού ανήκει η διαγραφή.
Η γόμα πινελιών ήρθε πρώτη
Η πρώτη γόμα διέγραφε ολόκληρες πινελιές. Σε κάθε άγγιγμα ελέγχεται κάθε πινελιά: τα πλαίσια οριοθέτησης απορρίπτουν σχεδόν όλες, και οι υπόλοιπες υποβάλλονται σε έλεγχο του τετραγώνου της απόστασης σημείου-προς-τμήμα ως προς την ακτίνα της γόμας συν την ακτίνα της πινελιάς, η οποία παρεμβάλλεται κατά μήκος του συγκεκριμένου τμήματος. Το πλάτος μεταβάλλεται με την πίεση, οπότε ένα μοναδικό ονομαστικό πλάτος θα διέγραφε λεπτές πινελιές που ο χρήστης δεν άγγιξε και θα αστοχούσε σε παχιές που άγγιξε.
Μία χειρονομία συγχωνεύεται σε μία εγγραφή αναίρεσης, και η εγγραφή αποθηκεύει κάθε πινελιά που αφαιρέθηκε μαζί με τον αρχικό της δείκτη. Η αναίρεση τις εισάγει ξανά με αντίστροφη σειρά, οπότε η σειρά στοίβαξης επανέρχεται ακριβώς όπως ήταν.
Αυτή η γόμα είναι γρήγορη, προβλέψιμη και ανίκανη να αφαιρέσει μισή λέξη.
Η MaLiang κάνει τη γόμα πινέλο
Το MaLiang, το πλαίσιο σχεδίασης με άδεια MIT από το οποίο έχουμε ήδη δανειστεί το μοντέλο σφραγίδων του μαρκαδόρου επισήμανσης, απαντά στη μερική διαγραφή σε είκοσι τέσσερις γραμμές. Η Eraser είναι υποκλάση της Brush που παρακάμπτει ένα μόνο πράγμα, τις επιλογές ανάμειξης, ώστε να αφαιρεί αντί να προσθέτει. Μια πινελιά γόμας είναι στοιχείο στην ίδια λίστα με κάθε άλλη πινελιά. Η αναίρεση αφαιρεί αυτό το στοιχείο. Καμία υπάρχουσα πινελιά δεν τροποποιείται, επειδή η διαγραφή συμβαίνει μόνο κατά την επαναπαραγωγή, με τη σειρά της λίστας.
Διαθέσαμε αυτό το μοντέλο, και δύο συνεδρίες σε συσκευές το έσπασαν.
Η μετακίνηση αντικειμένων αποκαλύπτει τον κάτοχο που έλειπε
Η πρώτη αναφορά: επιλέξτε πινελιές με το λάσο, σύρετέ τις, και το κενό μένει πίσω.

Οι ίδιες διαγραμμένες πινελιές, μετακινημένες προς τα δεξιά. Πάνω: η γόμα είναι δικό της στοιχείο. Κάτω: η διαγραφή ανήκει στις πινελιές.
Η επιλογή το έκανε χειρότερο. Οι επιλεγμένες πινελιές βγαίνουν από το bitmap σε μια επικάλυψη που τις σχεδιάζει απευθείας, χωρίς το στοιχείο γόμας, οπότε μόλις επέλεγες μια διαγραμμένη πινελιά, εμφανιζόταν ξανά ολόκληρη.
Η δεύτερη αναφορά: η γόμα αιωρούνταν πάνω από τον μαρκαδόρο επισήμανσης. Ο αποδότης κάνει επαναπαραγωγή σε δύο περάσματα, πρώτα τους μαρκαδόρους επισήμανσης και μετά τις πένες, επειδή το μελάνι του μαρκαδόρου επισήμανσης πρέπει να βρίσκεται κάτω από το μελάνι. Ένα στοιχείο γόμας πρέπει να συμμετέχει και στα δύο περάσματα, αλλιώς δεν μπορεί να σβήσει και τα δύο είδη. Η συμμετοχή στο πέρασμα της πένας σημαίνει ότι εκτελείται μετά την απόδοση κάθε μαρκαδόρου επισήμανσης, οπότε η γόμα έκοβε επίσης πινελιές μαρκαδόρου επισήμανσης που είχαν σχεδιαστεί αργότερα χρονικά.
Και οι δύο αστοχίες έχουν την ίδια αιτία. Η MaLiang δεν έχει λάσο, μετακίνηση ή σειρά στρώσεων ανάμεσα στα πινέλα, οπότε ένα στοιχείο χωρίς κάτοχο αρκεί εκεί. Ο δικός μας καμβάς έχει και τα τρία, και μια διαγραφή χωρίς κάτοχο δεν έχει καθορισμένη απάντηση στο «τι συμβαίνει όταν μετακινείται το μελάνι» ή στο «σε ποια στρώση λειτουργεί αυτό».
Η διαγραφή ανήκει στην πινελιά
Κάθε Stroke φέρει πλέον τις δικές του διαδρομές διαγραφής. Η γόμα ελέγχει ποιες πινελιές αγγίζει και προσθέτει σε καθεμία μια διαδρομή, σε συντεταγμένες καμβά. Η απόδοση μίας πινελιάς σχεδιάζει το μελάνι της και τις δικές της διαγραφές μέσα σε ένα επίπεδο διαφάνειας:
context.beginTransparencyLayer(auxiliaryInfo: nil)
drawInk(stroke, in: context, clip: clip, colorOverride: colorOverride)
context.setBlendMode(.destinationOut)
for path in stroke.erasePaths { fillErasePath(path, in: context) }
context.endTransparencyLayer()
Το Destination-out κρατά τον προορισμό και τον αφαιρεί ανάλογα με την alpha της πηγής:
Μέσα σε ένα επίπεδο διαφάνειας, ο προορισμός είναι η συγκεκριμένη πινελιά, οπότε η πράξη δεν μπορεί να φτάσει στο χαρτί, στις πινελιές από κάτω ή σε εκείνες που σχεδιάζονται αργότερα. Το επίπεδο δεν κοστίζει τίποτα για πινελιές χωρίς διαγραφές, δηλαδή για σχεδόν όλες: αυτές ακολουθούν την αρχική διαδρομή.
Η σύνδεση της διαγραφής με την πινελιά απαντά άμεσα στις δύο αστοχίες. Η μετατόπιση μετακινεί τις διαδρομές διαγραφής μαζί με τα σημεία δειγματοληψίας, άρα μια μετακινημένη πινελιά κουβαλά τα κενά της. Η επικάλυψη επιλογής σχεδιάζει το ίδιο αντικείμενο πινελιάς, οπότε μια επιλεγμένη πινελιά δείχνει τα ίδια κενά. Η σειρά στρώσεων παύει να έχει σημασία, επειδή μια διαγραφή δεν εφαρμόζεται ποτέ σε τίποτα εκτός από τη δική της πινελιά.
Η αναίρεση λειτουργεί σε επίπεδο χειρονομίας: μία εγγραφή παραθέτει κάθε ζεύγος (stroke, path) που παρήγαγε η χειρονομία, η αναίρεση αφαιρεί αυτές τις διαδρομές και η επανάληψη τις προσθέτει ξανά. Το μελάνι κάτω από μια διαγραφή δεν τροποποιείται ποτέ, οπότε δεν χρειάζεται να ανακατασκευαστεί τίποτα.
Η ζωντανή ανάδραση περνά από το bitmap πλακιδίων
Η πινελιά που σχεδιάζεται αποδίδεται κανονικά ως επικάλυψη πάνω από τη συντεθειμένη σελίδα. Η διαγραφή δεν μπορεί να λειτουργήσει εκεί, επειδή η επικάλυψη βρίσκεται πάνω από το χαρτί όσο και πάνω από το μελάνι και το destination-out θα τρυπούσε και το χαρτί.
Έτσι, η γόμα γράφει αμέσως μέσα στις πινελιές, και κάθε δέσμη νέων δειγμάτων ενεργοποιεί μια τοπική επαναπαραγωγή μόνο του επηρεαζόμενου ορθογωνίου. Η διοχέτευση πλακιδίων ήδη καθαρίζει ένα ορθογώνιο και επαναλαμβάνει την απόδοση των πινελιών που το τέμνουν· η γόμα επαναχρησιμοποιεί αυτή τη διαδρομή και δεν προσθέτει δεύτερη σημασιολογία σχεδίασης.
Οι δύο γόμες, δίπλα δίπλα
Η γραμμή εργαλείων έχει δύο κουμπιά γόμας αντί για ένα κουμπί με κρυφή λειτουργία. Η μερική γόμα αφαιρεί τμήμα μιας πινελιάς. Η γόμα πινελιών αφαιρεί ολόκληρες πινελιές κατά την επαφή. Οι ακτίνες τους είναι ξεχωριστές σταθερές, και οι δύο είναι προς το παρόν 4 μονάδες.
Η διαγραφή μιας ολόκληρης πινελιάς παίρνει μαζί της και τις διαδρομές διαγραφής της, αφού αποτελούν μέρος αυτής της πινελιάς, και η αναίρεση επαναφέρει και τα δύο μαζί. Οι διαδρομές διαγραφής αποτελούν επίσης μέρος του αποθηκευμένου αρχείου, κβαντισμένες όπως όλα τα υπόλοιπα: ακτίνα και συντεταγμένες σε δέκατα της μονάδας.
Ένας περιορισμός παραμένει. Μια πινελιά που έχει σβηστεί μέχρι να μη μείνει τίποτα εξακολουθεί να υπάρχει ως δεδομένα. Αποδίδεται ως τίποτα, αλλά το λάσο μπορεί ακόμη να την επιλέξει, η γόμα πινελιών μπορεί να τη διαγράψει και εξακολουθεί να καταλαμβάνει χώρο στο έγγραφο. Η συλλογή πλήρως διαγραμμένων πινελιών απαιτεί έναν έλεγχο κάλυψης που δεν έχουμε γράψει, οπότε προς το παρόν παραμένουν.
Όταν ένα μοντέλο από άλλο έργο παύει να λειτουργεί στο δικό σας, η διαφορά συνήθως δεν βρίσκεται στον κώδικα που αντιγράψατε. Βρίσκεται στις δυνατότητες που εκείνο το έργο δεν έχει.