दो इरेज़र, और मिटाने का स्वामित्व स्ट्रोक के पास क्यों है
इरेज़र स्ट्रोक को अलग एलिमेंट मानना सहज डिज़ाइन है। ऑब्जेक्ट को खिसकाना इसे तोड़ देता है, इसलिए Lulucat Notes में मिटाना उसी स्ट्रोक का है जिससे स्याही हटती है।
नोटबुक में मिटाने का मतलब दो अलग चीज़ें हो सकता है। कभी कोई निशान गलत होता है और पूरा निशान हट जाना चाहिए: कोई guide line, काटा हुआ शब्द, या आराम करती हथेली से बना stray stroke। कभी केवल किसी निशान का एक हिस्सा गलत होता है: किसी अक्षर का आधा भाग, diagram की एक शाखा, या highlighter sweep की वह पूँछ जो sentence से आगे निकल गई। Lulucat Notes अब दोनों देता है, दो अलग tools के रूप में।
मुश्किल डिज़ाइन सवाल यह है कि मिटाना रहता कहाँ है।
स्ट्रोक इरेज़र पहले आया
पहला इरेज़र पूरे strokes मिटाता था। एक touch हर stroke को जाँचता है: bounding boxes लगभग सभी को खारिज कर देते हैं, और बचे हुए strokes eraser radius तथा उस segment के साथ interpolated stroke radius के योग के विरुद्ध point-to-segment squared distance test से जाँचे जाते हैं। Width pressure के साथ बदलती है, इसलिए एक nominal width उन पतले strokes को मिटा देती जिन्हें उपयोगकर्ता ने छुआ ही नहीं था और छुए गए मोटे strokes को छोड़ देती।
एक gesture एक undo entry में merge होता है, और entry हर हटाए गए stroke को उसके original index के साथ रखती है। Undo उन्हें reverse order में फिर insert करता है, इसलिए stacking order बिल्कुल पहले जैसा लौटता है।
यह इरेज़र तेज़, पूर्वानुमेय और आधा शब्द मिटाने में असमर्थ है।
MaLiang इरेज़र को ब्रश बनाता है
MaLiang, वह MIT-licensed drawing framework जिससे हमने highlighter का stamp model पहले उधार लिया था, partial erasing का जवाब चौबीस lines में देता है। इसका Eraser, Brush subclass है जो केवल एक चीज़—blend options—को override करता है, ताकि add करने के बजाय subtract करे। Eraser stroke हर दूसरे stroke की तरह उसी list में एक element है। Undo उस element को हटाता है। कोई मौजूदा stroke modify नहीं होता, क्योंकि erasure केवल replay के दौरान, list order में होती है।
हमने वह model ship किया, और दो device sessions ने उसे तोड़ दिया।
ऑब्जेक्ट को खिसकाने से मालिक की कमी उजागर हुई
पहली रिपोर्ट: lasso से strokes select करके उन्हें drag करें, तो hole पीछे रह जाता है।

वही erased strokes, दाईं ओर move किए गए। ऊपर: eraser अपना अलग element है। नीचे: erasure strokes के स्वामित्व में है।
Selection ने इसे और खराब किया। Selected strokes bitmap से overlay में चले जाते हैं, जहाँ वे eraser element के बिना सीधे draw होते हैं, इसलिए जैसे ही erased stroke select किया गया, वह फिर पूरा दिखाई देने लगा।
दूसरी रिपोर्ट: eraser highlighter के ऊपर तैर रहा था। हमारा renderer दो passes में replay करता है, पहले highlighters और फिर pens, क्योंकि highlighter ink को pen ink के नीचे रहना होता है। Eraser element को दोनों passes में भाग लेना पड़ता है, वरना वह दोनों प्रकार के strokes को erase नहीं कर सकता। Pen pass में भाग लेने का अर्थ है हर highlighter के draw होने के बाद चलना, इसलिए eraser उन highlighter strokes को भी काट देता था जो समय में बाद में draw किए गए थे।
दोनों विफलताओं का स्रोत एक ही है। MaLiang में lasso, move या brushes के बीच layer order नहीं है, इसलिए बिना owner वाला element वहाँ पर्याप्त है। हमारे canvas में ये तीनों हैं, और स्वामीरहित erasure के लिए “ink move होने पर क्या होता है” या “यह किस layer पर काम कर रही है” का निश्चित उत्तर नहीं है।
मिटाने का स्वामित्व स्ट्रोक के पास
अब हर Stroke अपने erase paths रखता है। Eraser जिन strokes को छूता है, उन्हें test करके हर एक में canvas coordinates में एक path append करता है। एक stroke render करते समय उसकी ink और उसकी अपनी erasures को transparency layer के भीतर draw किया जाता है:
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 destination को बनाए रखता है और source alpha के अनुपात में उसे हटाता है:
Transparency layer के भीतर destination वही एक stroke है, इसलिए operation paper, नीचे के strokes या बाद में draw हुए strokes तक नहीं पहुँच सकता। बिना erasures वाले strokes के लिए layer की कोई लागत नहीं है—और ऐसे strokes लगभग सभी हैं: वे original path लेते हैं।
स्वामित्व सीधे दोनों विफलताओं का जवाब देता है। Translation erase paths को sample points के साथ move करता है, इसलिए moved stroke अपने gaps साथ ले जाता है। Selection overlay उसी Stroke object को draw करता है, इसलिए selected stroke में वही gaps दिखते हैं। Layer order मायने रखना बंद कर देता है, क्योंकि erasure अपने stroke के अलावा किसी चीज़ पर लागू नहीं होती।
Undo gesture पर काम करता है: एक entry में उस gesture से बने हर (stroke, path) pair की सूची होती है; undo उन paths को हटाता है, redo उन्हें फिर append करता है। Erasure के नीचे की ink कभी edit नहीं होती, इसलिए कुछ reconstruct नहीं करना पड़ता।
टाइल बिटमैप के ज़रिए लाइव फ़ीडबैक मिलता है
जो stroke draw हो रहा है, उसे सामान्यतः composited page के ऊपर overlay के रूप में render किया जाता है। वहाँ erasure काम नहीं कर सकती, क्योंकि overlay paper और ink दोनों के ऊपर बैठती है, और destination-out paper में भी छेद कर देगा।
इसलिए eraser तुरंत strokes में लिखता है, और नए samples के हर batch पर केवल affected rectangle का local replay होता है। Tile pipeline पहले से rectangle clear करके उसमें intersect करने वाले strokes replay करता है; eraser उसी path को reuse करता है और drawing की कोई दूसरी semantic नहीं जोड़ता।
दोनों इरेज़र साथ-साथ
Toolbar में एक hidden mode वाले एक button के बजाय दो eraser buttons हैं। Partial eraser stroke का एक हिस्सा हटाता है। Stroke eraser contact होते ही पूरे strokes हटाता है। उनकी त्रिज्याएँ अलग-अलग स्थिरांक हैं, और फ़िलहाल दोनों का मान चार पॉइंट है।
पूरे stroke को delete करने पर उसके erase paths भी चले जाते हैं, क्योंकि वे उसी stroke का हिस्सा हैं, और undo दोनों को साथ वापस लाता है। Erase paths saved file का भी हिस्सा हैं, बाकी data की तरह quantize किए जाते हैं: radius और coordinates point के tenths में।
एक limitation बाकी है। पूरी तरह मिटा दिया गया stroke data के रूप में अब भी मौजूद रहता है। वह कुछ render नहीं करता, लेकिन lasso उसे अब भी select कर सकता है और stroke eraser उसे delete कर सकता है; वह document में जगह भी घेरता है। पूरी तरह erased strokes को collect करने के लिए coverage test चाहिए, जिसे हमने अभी लिखा नहीं है, इसलिए फिलहाल वे बने रहते हैं।
जब किसी दूसरे project का model आपके project में काम करना बंद कर दे, तो अंतर आम तौर पर उस code में नहीं होता जिसे आपने copy किया है। वह उन features में होता है जो उस project में हैं ही नहीं।