2 poin oleh GN⁺ 2024-03-06 | 1 komentar | Bagikan ke WhatsApp
  • Paper berjalan sebagai editor TextView berbasis TextKit 1 di platform Apple, dengan NSTextStorage, NSTextContainer, NSLayoutManager, TextView, dan ScrollView yang bersama-sama membentuk pengalaman penyuntingan
  • NSAttributedString merepresentasikan rich text melalui string dan atribut (attribute) per rentang; Paper memisahkan atribut meta untuk struktur Markdown dan atribut gaya untuk tampilan layar
  • Pembaruan gaya berbeda tergantung pada pembukaan dokumen, perubahan teks, dan perubahan pengaturan; atribut yang memengaruhi tata letak dibundel dalam transaksi, sedangkan atribut dekoratif diperbarui tanpa menghitung ulang tata letak
  • Performa input ditingkatkan dengan menghindari parsing ulang seluruh paragraf berdasarkan apakah penyuntingan terjadi di sekitar karakter khusus Markdown, tetapi struktur yang dapat memengaruhi gaya seluruh dokumen seperti blok kode perlu ditangani sebagai pengecualian
  • Salin-tempel bekerja di atas UTI dan berbagai representasi data pada pasteboard; UTI semipublik yang tidak didefinisikan Apple, seperti Markdown, dapat menimbulkan masalah konversi RTF dan UX karena perbedaan prioritas antar-aplikasi

Stack penyuntingan TextView Apple

  • Paper dibuat di atas framework TextKit 1 yang sudah lama, dan konsep serta abstraksi yang sama tetap ada di TextKit 2 atau hadir di bawah API yang lebih baik
  • NSTextView dan UITextView memiliki perbedaan, tetapi API-nya cukup mirip sehingga dapat diperlakukan sebagai satu keluarga TextView
  • TextView adalah komponen besar yang makin kompleks di setiap rilis OS, dan aplikasi TextEdit hampir seluruhnya terdiri dari satu TextView
  • Lapisan inti penyusun editor

    • NSTextStorage
      • Menyimpan string teks asli
      • Menyimpan atribut berupa pasangan string-nilai yang melekat pada rentang teks
      • Memicu event perubahan teks dan atribut
    • NSTextContainer
      • Mendefinisikan bentuk dan ukuran area tempat glyph ditempatkan
      • Umumnya berbentuk persegi panjang, tetapi bentuk arbitrer juga dimungkinkan
    • NSLayoutManager
      • Melihat rentang atribut pada NSTextStorage dan menghitung ukuran serta jarak glyph
      • Mengekstrak glyph vektor dari font, dan mengubah satu karakter menjadi satu atau beberapa glyph
      • Menghitung awal dan akhir setiap baris, jumlah baris, serta tinggi total teks
    • TextView
      • Menggambar layout glyph yang dibuat oleh NSLayoutManager
      • Menyinkronkan tinggi view dengan tinggi layout teks saat ini
      • Mengelola input, seleksi, caret, atribut input, textContainerInset, dikte, salin-tempel, pemeriksaan ejaan, dan lainnya
    • ScrollView
      • Mengelola bagian TextView yang terlihat, scrolling, scrollbar, dan zoom
      • Dapat memiliki contentInset yang terpisah dari textContainerInset milik TextView
  • Perbedaan scrolling antara AppKit dan UIKit

    • Di AppKit, NSScrollView berisi NSClipView dan dua NSScroller, lalu NSClipView menampung NSTextView, sehingga beberapa class bersama-sama membentuk perilaku scrolling
    • Di UIKit, UITextView mewarisi UIScrollView, sehingga menyertakan langsung logika scrolling
    • Jika caret bergerak keluar dari area terlihat yang dibatasi oleh contentInset, UITextView akan melakukan scroll otomatis
    • Pada editor iOS, perilaku ketika caret masuk ke belakang keyboard lalu layar discroll ke baris berikutnya terjadi karena contentInset bawah diatur secara dinamis ke tinggi keyboard saat ini

NSAttributedString dan pencarian atribut

  • NSAttributedString adalah fondasi penyuntingan rich text dalam framework Apple dan terdiri dari dua elemen
    • String teks biasa
    • Pasangan atribut string-nilai yang melekat pada rentang teks di dalam string
  • Atribut terutama digunakan untuk styling, tetapi developer juga dapat melekatkan pasangan string-nilai arbitrer yang dibutuhkan
  • NSRange terdiri dari location dan length; NSMakeRange(10, 5) berarti rentang 5 karakter yang dimulai pada posisi 10, yaitu posisi 10–14
  • Jika atribut yang sama ditetapkan pada beberapa rentang di posisi yang sama, rentang yang diterapkan terakhir akan diprioritaskan
  • Memeriksa atribut dan rentang pada posisi tertentu

    • Nilai atribut pada posisi tertentu diperiksa dengan attribute:atIndex:effectiveRange:
    • Jika nilai kembalian nil, atribut tersebut tidak ada; jika ada nilai kembalian, dalam contoh ini nilainya adalah objek atribut seperti NSFont atau UIFont
    • Jika pointer NSRange diberikan sebagai argumen terakhir, kita dapat menerima rentang berlanjut dengan nilai atribut yang sama atau rentang kosong tanpa atribut
    • Menurut dokumentasi, effectiveRange tidak harus merupakan rentang maksimum dan bergantung pada implementasi
    • Jika membutuhkan rentang maksimum yang dijamin, gunakan attribute:atIndex:longestEffectiveRange:inRange:
    • Ada juga method untuk memperoleh semua kombinasi atribut pada posisi tertentu beserta rentang tempat kombinasi itu berlanjut
    • Untuk mengiterasi atribut dalam rentang, dapat digunakan enumerateAttribute:inRange:options:usingBlock:

Pipeline styling Markdown di Paper

  • Styling Paper dilakukan dengan menerapkan atribut khusus yang didefinisikan framework pada rentang teks
  • Sebelum styling, atribut meta kustom juga digunakan untuk mengidentifikasi struktur teks
  • Atribut meta dan atribut gaya

    • Atribut meta
      • Didefinisikan oleh parser Markdown untuk mengidentifikasi bagian-bagian individual dari sintaks Markdown
      • Merupakan pasangan string-nilai kustom murni untuk informasi semantik
      • Tidak memengaruhi tampilan visual teks
      • Dapat dilihat seperti AST yang disederhanakan untuk Markdown
    • Atribut gaya
      • Atribut visual yang diterapkan di atas bagian yang ditandai oleh atribut meta
      • Merupakan pasangan string-nilai bawaan yang didefinisikan AppKit dan UIKit
  • Event yang memicu pembaruan atribut

    • Atribut disinkronkan dengan teks Markdown di NSTextStorage dan perubahan pengaturan yang memengaruhi teks
    • Pada pembukaan dokumen, atribut meta dan atribut gaya diperbarui seluruhnya
    • Pada perubahan teks, atribut meta dan atribut gaya pada bagian yang terdampak diperbarui sebagian
    • Pada perubahan pengaturan, atribut meta tidak diubah dan hanya atribut gaya yang diperbarui seluruhnya
  • Urutan pembaruan dan biaya layout

    • Pertama, transaksi penyuntingan teks dimulai untuk membundel perubahan atribut
    • Jika atribut diubah tanpa transaksi, setiap perubahan akan memicu penghitungan ulang layout oleh NSLayoutManager yang mahal
    • String Markdown di-parse dan dibagi menjadi potongan yang ditandai dengan atribut meta; saat pengaturan berubah, langkah ini dilewati karena struktur Markdown tidak berubah
    • Atribut visual yang dapat memengaruhi posisi atau ukuran glyph diperbarui terlebih dahulu, dan NSParagraphStyle diperlakukan sebagai atribut paling penting yang memengaruhi layout baris dan paragraf
    • Setelah transaksi selesai, atribut dekoratif yang tidak memengaruhi layout diperbarui
    • Dalam istilah Apple, atribut dekoratif adalah atribut rendering; karena berada di NSLayoutManager, bukan NSTextStorage, atribut ini tidak mengenali transaksi
  • Atribut input

    • Atribut input (typing attributes) terhubung ke atribut pada posisi sebelum caret ketika seleksi kosong, dan ke atribut pada titik awal seleksi ketika seleksi tidak kosong
    • Ketika karakter diketik, atribut input otomatis diterapkan pada teks yang baru disisipkan
    • Pada editor Markdown, atribut ini kurang penting karena gaya diturunkan dari sintaks Markdown
    • Pada editor rich text, atribut ini lebih penting karena gaya tetap melekat pada caret sampai gaya dimatikan atau caret dipindahkan ke posisi lain
    • Preview Mode di Paper menyediakan pengalaman penyuntingan rich text di dalam editor Markdown, dengan atribut input yang dapat di-toggle menjadi lebih menonjol

Performa input dan pemanfaatan atribut meta

  • Pemisahan atribut meta, layout, dan dekoratif menguntungkan untuk memproses sebagian perubahan editor dengan cepat
  • Peralihan mode terang/gelap hanya memperbarui atribut dekoratif, sehingga tidak memicu layout dan sangat cepat
  • Perubahan pengaturan seperti penyesuaian ukuran teks memerlukan penghitungan ulang layout seluruh dokumen, tetapi tetap lebih cepat daripada mem-parse ulang seluruh struktur Markdown
  • Optimasi input Markdown

    • Faktor performa terpenting dalam editor teks adalah kecepatan input
    • Karena karakteristik Markdown, satu perubahan teks dapat memengaruhi gaya seluruh paragraf
    • Mem-parse dan men-style ulang seluruh paragraf pada setiap penekanan tombol adalah cara yang secara teknis paling akurat, tetapi dapat memperlambat penyuntingan pada paragraf panjang
    • Paper menggunakan algoritme yang membedakan perilaku dengan melihat karakter berikut yang diinput dan karakter di sekitarnya
    • Jika simbol khusus Markdown diketik atau ada simbol khusus di sekitar posisi penyuntingan, seluruh paragraf diperbarui
    • Jika tidak, Paper mengandalkan atribut input untuk meningkatkan kecepatan editor dalam sebagian besar situasi input
  • Pengecualian blok kode dan cache objek nilai

    • Blok kode adalah satu-satunya struktur Markdown multiparagraf di Paper
    • Dalam dokumen yang memiliki blok kode, satu input tombol dapat memengaruhi gaya seluruh dokumen
    • Pada dokumen yang melebihi jumlah karakter tertentu, Paper mengabaikan pemrosesan blok kode agar editor tetap cepat bagi mayoritas pengguna yang tidak peduli dengan kode
    • Pilihan ini juga membuat Paper lebih berguna bagi pengguna yang berdekatan dengan developer
    • Objek nilai atribut yang kompleks dialokasikan ulang pada setiap input tombol, tetapi karena tidak berubah selama pengaturan yang memengaruhi teks tidak berubah, objek tersebut di-cache dan digunakan kembali
      • NSFont/UIFont
      • NSColor/UIColor
      • NSParagraphStyle
  • Fitur yang menggunakan atribut meta

    • Shortcut pemformatan
      • Informasi gaya yang sudah ada pada teks Markdown terpilih perlu diketahui agar toggle dapat dilakukan
      • Jika seleksi sepenuhnya mencakup gaya yang sama, gaya tersebut dihapus
      • Jika seleksi tidak memiliki gaya yang sama, gaya ditambahkan
      • Jika seleksi hanya mencakup sebagian gaya yang sama, gaya dipindahkan ke seleksi
      • Gaya yang saling bertabrakan dan mendefinisikan tipe paragraf, seperti heading dan blockquote, tidak dapat dicampur sehingga harus dihapus sebelum gaya baru ditambahkan
    • Navigasi bab
      • Saat berpindah ke batas bab sebelumnya atau berikutnya, atribut meta digunakan untuk menemukan heading berdasarkan posisi caret
    • Outline
      • Semua heading diiterasi, dan ketika item outline ditekan, caret berpindah ke bab tersebut
    • Penataan ulang bab
      • Bab dapat disusun ulang dari outline
    • Konversi format
      • Untuk mengonversi Markdown ke RTF, HTML, atau DOCX, struktur teks perlu diketahui
      • Karena Paper tidak menyertakan library eksternal, Paper mengiterasi model teks yang sudah di-parse sebelumnya dan membuat format output

Matematika text container dan perilaku seleksi

  • Perhitungan panjang baris dan margin

    • Aturan inti text container adalah mempertahankan panjang baris yang disukai dan membagi ruang tersisa menjadi inset di kedua sisi
    • Ada juga situasi ketika simetri perlu dipalsukan, seperti saat tag heading ditempatkan di luar alur teks biasa
    • Dalam kondisi ini, text container digeser ke kiri dan paragraf diindentasi dengan NSParagraphStyle
    • Jika ruang cukup, margin dipertahankan agar secara visual sedekat mungkin dengan simetris
    • Jika tidak ada ruang tambahan, simetri dikorbankan demi mempertahankan panjang baris yang ditentukan, tetapi hanya selama padding kanan masih tersisa
    • Jika padding tidak tersisa, margin minimum diprioritaskan di atas lebar baris yang disukai
    • Kombinasi min dan max dapat digunakan untuk mengimplementasikan margin yang terlipat secara bertahap
  • Anchor seleksi

    • Seleksi teks selalu memiliki titik anchor
    • Di Mac, saat klik lalu drag, titik klik menjadi acuan; jika titik itu dilewati, arah pertambahan atau pengurangan seleksi berbalik
    • Di iOS, ketika salah satu handle seleksi di-drag, sisi sebaliknya menjadi anchor, dan begitu pula sebaliknya
    • Logika yang sama berlaku saat memperluas seleksi dengan keyboard
    • Dengan Option+panah kiri/kanan, caret berpindah ke batas kata; jika Shift juga ditekan, seleksi dilakukan per kata
    • Setelah memilih dengan klik-drag, meskipun seleksi terus diperluas atau diperkecil dengan keyboard, titik klik awal tetap menjadi anchor
  • Affinity seleksi

    • Affinity seleksi menentukan apakah insertion point pada batas pergantian baris muncul setelah karakter terakhir baris sebelumnya atau sebelum karakter pertama baris berikutnya
    • Saat caret digerakkan dengan tombol panah, di sekitar spasi pada titik pergantian baris, editor hanya berpindah baris
    • Saat shortcut digunakan untuk berpindah ke akhir baris, caret tetap berada di baris yang sama dan dapat menempel di sisi kanan spasi pergantian baris
    • TextView melakukan perilaku affinity seleksi serupa dalam situasi lain juga
  • Undo coalescing

    • Undo coalescing adalah algoritme yang menentukan kapan teks yang diketik digabungkan menjadi satu aksi yang dapat di-undo
    • TextView tampaknya menggunakan algoritme sederhana yang menggabungkan input yang tidak disela oleh aksi lain
    • Meski berhenti sejenak lalu lanjut mengetik, semuanya digabungkan menjadi satu undo, dan line break juga tidak memutus coalescing
    • Sublime lebih memilih cara mengelompokkan kata-kata terpisah selama input sebagai unit undo, dan kadang membagi unit undo lebih kecil saat mengetik kata panjang
    • Tidak ada jawaban benar untuk perilaku ini, dan tiap editor dapat memiliki strategi berbeda

UTI, pasteboard, dan pertukaran data antar-aplikasi

  • Uniform Type Identifiers

    • UTI adalah sistem hierarkis tempat tipe data conform to tipe data induknya seolah mewarisinya
    • Tipe public.* didefinisikan Apple dan mengidentifikasi format yang banyak digunakan seperti public.html dan public.jpeg
    • Developer dapat membuat identifier sendiri dengan skema reverse-domain-name untuk menghindari konflik
    • Keunggulan sistem hierarkis adalah jika sebuah aplikasi dapat melihat semua format teks, ia cukup mendeklarasikan public.text tanpa perlu mencantumkan setiap format satu per satu
    • Paper menyatakan dapat membuka semua file teks; meski tanpa highlighting, Paper dapat membuka format teks seperti .html dan .rtf
    • Karena file adalah konsep lintas platform, pada praktiknya ekstensi file digunakan sebagai identifier
    • Untuk kompatibilitas, semua UTI dapat mendefinisikan satu atau lebih ekstensi file terkait
    • Markdown banyak digunakan, tetapi karena bukan format yang didefinisikan Apple, muncul kasus rumit terkait UTI semipublik
  • Pasteboard dan berbagai representasi

    • Pasteboard Apple adalah dictionary yang memetakan UTI ke teks terserialisasi atau data biner
    • Dengan Clipboard Viewer dari Xcode Additional Tools, isi pasteboard dapat dilihat secara real time
    • Satu aksi copy menulis beberapa representasi dari data yang sama sekaligus
    • Sebagian aplikasi juga menggunakan identifier non-UTI legacy seperti NeXT Rich Text Format v1.0 pasteboard type demi kompatibilitas mundur
    • Editor umumnya memilih format paling kaya yang dapat diprosesnya
    • Paste and Match Style atau Paste as Plain Text pada editor rich text membuat editor menggunakan format teks biasa dari pasteboard
    • Gaya yang diterapkan pada teks yang ditempel biasanya diambil dari atribut input
    • Drag and drop juga bekerja berdasarkan pasteboard lain
    • Copy-paste standar menggunakan general pasteboard, dan untuk interaksi antar-aplikasi khusus, pasteboard kustom dapat dibuat
  • RTF dan NSAttributedString

    • RTF pada dasarnya adalah bentuk serialisasi dari NSAttributedString, sedangkan sebaliknya NSAttributedString adalah antarmuka pemrograman untuk RTF
    • Karena TextView bekerja di atas NSTextStorage, subclass dari NSAttributedString, ia pada dasarnya kompatibel dengan pasteboard
    • Konten dapat disalin ke pasteboard tanpa coding terpisah
  • Masalah antar-aplikasi pada UTI Markdown

    • Dalam salin-tempel antar-editor Markdown, masalah dapat muncul meskipun kedua aplikasi menerapkan protokol standar
    • Jika editor pertama mengekspor Markdown sebagai public.text dan representasi rich text sebagai public.rtf, editor kedua tidak dapat mengetahui bahwa itu Markdown native dan bisa memilih public.rtf
    • Dalam kasus ini terjadi konversi ganda yang tidak perlu, dan karena tiap aplikasi memiliki cara konversi Markdown↔RTF yang berbeda, masalah pemformatan kecil seperti line break tambahan dapat muncul
    • Perbedaan gaya mendasar antara Markdown dan RTF juga dapat terlihat
    • Agar perilakunya natural, kedua aplikasi harus mengekspor UTI net.daringfireball.markdown dan memprioritaskannya di atas public.rtf
    • Paper mencoba mengekspor UTI Markdown, tetapi tampaknya aplikasi lain tidak memprioritaskannya di atas rich text
    • Pages memprioritaskan net.daringfireball.markdown di atas public.rtf, tetapi menunjukkan perilaku menyisipkan string Markdown asli apa adanya tanpa mengonversinya menjadi rich text
    • Karena itu, Paper menghapus UTI Markdown
  • Alasan Paper menyediakan RTF dan berbagi di iOS

    • Paper menyediakan RTF agar salin-tempel ke editor rich text terasa natural
    • Sebagai warga OS yang baik, aplikasi perlu menyediakan beberapa format yang merepresentasikan data yang disalin agar aplikasi penerima dapat memilih format paling kaya yang dapat diprosesnya
    • Jika teks Markdown dari Paper disalin lalu ditempel ke aplikasi Mail, teks akan ditempel sebagai rich text yang terformat rapi, bukan sebagai variasi Markdown
    • Fitur berbagi di iOS mirip dengan copy-paste, tetapi ditambah UI
    • Dalam berbagi iOS, aplikasi mengekspor data dalam beberapa format dan aplikasi penerima memilih format yang diinginkan
    • Berbeda dari pasteboard, agar muncul di daftar aplikasi bagian atas pada berbagi iOS, aplikasi harus secara eksplisit menyediakan share extension dengan UI kustom

1 komentar

 
GN⁺ 2024-03-06
Komentar Hacker News
  • Benar-benar luar biasa. Sekarang, untuk materi pengantar TextKit, sepertinya saya akan membaca tulisan ini lebih dulu daripada https://www.objc.io/issues/5-ios7/getting-to-know-textkit/

    • Untuk iOS 15.0 ke atas atau MacOS Monterey ke atas, Anda harus melihat TextKit 2. Lihat https://developer.apple.com/documentation/appkit/textkit/usi...
      Ini adalah engine layout baru yang saat ini dipakai secara default di field teks iOS dan MacOS, dan kemungkinan besar akan menjadi satu-satunya pilihan ke depannya
  • Aplikasi TextEdit hampir seluruhnya terdiri dari satu TextView
    Padanan di Windows bisa dibilang WordPad yang berbasis kontrol RichEdit. Hal menarik lainnya adalah RTF pada dasarnya merupakan format serialisasi untuk NSAttributedString
    Kontrol RichEdit di Windows juga demikian, dan implementasi Windows tampaknya memang muncul lebih dulu: https://en.wikipedia.org/wiki/Rich_Text_Format

  • Bagian tentang menangani atribut dekorasi di luar transaksi pengeditan agak membingungkan. Ada penjelasan bahwa “karena berada di NSLayoutManager sendiri dan bukan di NSTextStorage, ia bahkan tidak tahu soal transaksi”, tetapi atribut dekorasi seperti warna biasanya berada di NSTextStorage
    Apakah maksud penulis adalah warna yang diterapkan pada karakter Markdown diproses lewat dukungan atribut sementara di NSLayoutManager? Itu biasanya dipakai untuk memberi warna pada kata yang salah eja; saya penasaran apa tujuannya

    • Benar. NSTextStorage juga bisa menyimpan atribut seperti atribut sementara. Di TextKit 2, ini disebut atribut rendering
      Keuntungan memakai atribut sementara adalah perubahannya jauh lebih cepat daripada memanggil seluruh transaksi NSTextStorage. Misalnya, di Paper, perpindahan mode terang/gelap sangat cepat
  • Tulisan yang sangat bagus, dan bagi saya pribadi juga tepat waktu karena saat ini sedang menangani NSTextView
    Saya penasaran bagaimana informasi yang diperlukan untuk menulis artikel seperti ini didapat. Dari kode orang lain, pengalaman menyakitkan, atau developer.apple.com?

    • Saya telah mengerjakan Paper selama 9 tahun, dan selama itu ada banyak waktu untuk mempelajari bagaimana seluruh strukturnya bekerja
  • Di era dokumen DOM, bahkan saat melihat Notion atau GitBook, ketika perlu melakukan hal-hal yang terasa ajaib dalam parsing dan manipulasi teks, saya sering kembali ke attributed string
    Strukturnya benar-benar elegan, dan saya tidak mengerti kenapa tidak lebih dikenal. Tulisannya juga luar biasa bagus

    • Kalau ada contoh, saya ingin belajar lebih lanjut
    • Saya penasaran apakah ada komponen editor rich text web yang menggunakan attributed string. Saya sudah lelah dengan DOM
  • Sebagai orang yang dulu pernah mencoba membuat editor teks sepenuhnya dari nol, tulisan seperti ini pasti akan menjadi sumber referensi yang luar biasa

  • Saya benar-benar memakai aplikasi ini dengan sangat baik. Ini menggantikan semua aplikasi Markdown lain, termasuk Obsidian dan iA Writer

    • Nama Paper memang cocok, tetapi kalau ingatan saya benar, setidaknya ada dua aplikasi iOS lain lagi yang bernama “Paper”
    • Menarik. Dua aplikasi utama yang saya pakai untuk menulis adalah dua aplikasi itu, jadi saya harus mencoba aplikasi ini juga
  • Saya berharap lebih banyak komponen iOS punya dokumentasi seperti ini

  • Senang rasanya pada 2024 masih ada orang yang mengerjakan Cocoa

  • Sebagai seseorang yang sudah lama mengembangkan aplikasi Android, menarik melihat cara Apple melakukan pendekatan yang sedikit berbeda dan lebih hati-hati
    Di Android, pada dasarnya semua hal terkait layout dan rendering ditangani oleh kelas Layout dan subclass-nya, sementara TextView mengimplementasikan sebagian logika pengeditan dan seleksi. Satu-satunya perbedaan antara EditText dan TextView adalah EditText mengaktifkan fitur pengeditan yang sebenarnya sudah ada di TextView
    Masalah dari pendekatan yang agak monolitik seperti ini dan API yang minim adalah Anda akan mentok ketika ingin mengontrol rendering teks aplikasi dengan lebih rinci. Misalnya, bahkan jika ingin mengakses glyph individual setelah layout selesai, itu tidak bisa dilakukan