Cấu trúc kỹ thuật bên trong của trình soạn thảo văn bản Apple
- Trình bày chi tiết cách Paper hoạt động như một trình soạn thảo văn bản dựa trên
TextView.
- Paper hiện được xây dựng trên framework TextKit 1, và trong TextKit 2, các khái niệm, lớp trừu tượng và nguyên tắc vẫn được giữ nguyên hoặc được thay đổi thành API tốt hơn.
Chế độ xem văn bản
- Lớp
TextView là cốt lõi của tác vụ chỉnh sửa văn bản trong trình soạn thảo văn bản của Apple.
NSTextView và UITextView có khác biệt, nhưng do API tương tự nhau nên được xem như một lớp TextView duy nhất.
TextView là một thành phần quy mô lớn với độ phức tạp tăng lên qua mỗi lần phát hành OS.
- Apple chia nhỏ
TextView thành nhiều tầng để cung cấp trải nghiệm chỉnh sửa văn bản.
NSTextStorage
- Lưu trữ chuỗi văn bản thô.
- Lưu trữ các thuộc tính (cặp chuỗi-giá trị) được gán cho các phạm vi văn bản.
- Phát sinh sự kiện cho các thay đổi về văn bản và thuộc tính.
NSTextContainer
- Định nghĩa hình dạng và kích thước của vùng chứa các glyph văn bản.
NSLayoutManager
- Xem các phạm vi thuộc tính được áp dụng cho chuỗi văn bản trong
NSTextStorage để tính kích thước và khoảng cách của glyph.
- Bố cục các glyph, tính vị trí bắt đầu và kết thúc của từng dòng văn bản, cùng chiều cao tổng thể của toàn bộ văn bản.
TextView
- Vẽ bố cục glyph do
NSLayoutManager tạo ra.
- Đồng bộ chiều cao của view với chiều cao hiện tại của văn bản đã được bố cục.
- Quản lý lựa chọn văn bản, con trỏ caret và các thuộc tính gõ được áp dụng cho văn bản mới chèn vào.
ScrollView
- Hiển thị phần nhìn thấy được của
TextView.
- Quản lý cuộn, thanh cuộn và phóng to/thu nhỏ.
Thuộc tính
NSAttributedString là nền tảng của chỉnh sửa rich text trong các framework của Apple.
- Nó gồm một chuỗi văn bản thuần và các thuộc tính (cặp chuỗi-giá trị) được gắn vào các phạm vi văn bản.
- Thuộc tính chủ yếu được dùng cho mục đích tạo kiểu, nhưng không có giới hạn trong việc gán các cặp chuỗi-giá trị tùy chỉnh.
Tạo kiểu
- Tạo kiểu có nghĩa là áp dụng các thuộc tính đặc biệt do framework định nghĩa lên các phạm vi văn bản.
- Paper dùng meta attribute để nhận diện cấu trúc văn bản rồi mới áp dụng tạo kiểu.
- Các thuộc tính được đồng bộ với văn bản Markdown trong
NSTextStorage thay đổi do đầu vào của người dùng và với các thiết lập ảnh hưởng đến văn bản mà người dùng điều chỉnh qua mục menu, thanh trượt và cử chỉ.
Hiệu năng
- Việc tách biệt các thuộc tính meta, bố cục và trang trí giúp duy trì nhanh các thay đổi cụ thể trong trình soạn thảo.
- Tốc độ gõ là yếu tố hiệu năng quan trọng nhất trong một trình soạn thảo văn bản.
- Do cách Markdown hoạt động, thay đổi văn bản có thể ảnh hưởng đến việc tạo kiểu của cả đoạn văn.
Meta attribute
- Ngoài logic tô sáng, meta attribute còn đóng vai trò quan trọng trong nhiều tính năng cần hiểu cấu trúc văn bản.
Phím tắt định dạng
- Cung cấp thông tin chi tiết cần thiết để bật/tắt kiểu của văn bản Markdown đã chọn.
Di chuyển giữa các chương
- Giúp tìm tiêu đề tương ứng với vị trí tương đối của caret.
Dàn ý (Outline)
- Phụ thuộc vào khả năng duyệt qua tất cả các tiêu đề.
Sắp xếp lại chương
- Cung cấp khả năng sắp xếp lại các chương trong dàn ý.
Chuyển đổi định dạng
- Cần biết cấu trúc để chuyển nội dung Markdown sang RTF, HTML, DOCX.
Toán học của text container
- Quy tắc quan trọng nhất của text container là duy trì độ dài dòng ưu tiên.
- Có những trường hợp cần giả lập tính đối xứng, chẳng hạn khi thẻ tiêu đề được đặt ra ngoài luồng văn bản thông thường.
Neo lựa chọn
- Việc chọn văn bản luôn có một điểm neo.
- Trên Mac, bạn chọn văn bản bằng cách nhấp và kéo; trên iOS, bạn có thể kéo một đầu của vùng chọn.
Affinity của lựa chọn
- Trong chỉnh sửa văn bản có một khái niệm thú vị gọi là selection affinity.
- Khi di chuyển caret bằng phím mũi tên, nó đơn giản chuyển dòng; nhưng khi dùng phím tắt để đi đến cuối dòng, nó bám về bên phải trong khi vẫn ở cùng dòng.
Uniform Type Identifiers (UTIs)
- Bài viết thảo luận về UTI, hệ thống nền tảng cho việc trao đổi dữ liệu giữa các ứng dụng.
- Đây là một hệ thống phân cấp trong đó các kiểu dữ liệu conform to kiểu dữ liệu cha.
Bảng tạm (Pasteboard)
- Bảng tạm là một từ điển ánh xạ các UTI với dữ liệu đã được tuần tự hóa.
- Một thao tác copy duy nhất sẽ ghi nhiều biểu diễn của cùng một dữ liệu cùng lúc.
- Việc xử lý UTI công khai và UTI riêng tương đối đơn giản, nhưng xử lý các định dạng được chấp nhận rộng rãi mà không do Apple định nghĩa thì phức tạp hơn.
Kết lại
- Nếu xem bài viết đầu tiên, bạn có thể biết thêm nhiều thông tin về ứng dụng và quá trình phát triển.
Ý kiến của GN⁺
- Bài viết này giải thích chi tiết cơ chế hoạt động nội bộ phức tạp của trình soạn thảo văn bản dựa trên
TextView trên nền tảng Apple, mang lại thông tin thú vị cho các nhà phát triển phần mềm hoặc người dùng quan tâm.
- Các thuật toán tối ưu hiệu năng và cách quản lý thuộc tính trong trình soạn thảo văn bản là những ví dụ tốt để các nhà phát triển tham khảo khi thiết kế ứng dụng của riêng mình.
- Cách tiếp cận kỹ thuật được dùng để nâng cao hiệu năng của trình soạn thảo văn bản cung cấp những chỉ dẫn hữu ích cho các nhà phát triển khác khi giải quyết các vấn đề tương tự.
- Khi phát triển ứng dụng xử lý các định dạng văn bản như Markdown, việc hiểu về UTI là quan trọng cho trao đổi dữ liệu và khả năng tương thích.
- Bài viết này giúp nâng cao hiểu biết về cấu trúc nội bộ của trình soạn thảo văn bản, nhưng việc thực sự quản lý những độ phức tạp đó vẫn có thể là một thách thức lớn với nhà phát triển.
1 bình luận
Ý kiến trên Hacker News
Bài này thật sự rất hay. Có lẽ nó sẽ thay thế https://www.objc.io làm tài liệu giới thiệu cơ bản về TextKit mà tôi thường dùng.
Tôi hơi bối rối về các thuộc tính trang trí được thực hiện bên ngoài transaction chỉnh sửa. Bài viết nói: "Và nó không nhận biết transaction, bởi vì nó tồn tại trong NSLayoutManager chứ không phải trong NSTextStorage." Nhưng các thuộc tính trang trí như màu sắc thường lại nằm trong NSTextStorage! Tác giả có đang ngụ ý rằng màu áp dụng cho các ký tự markdown được thực hiện thông qua hỗ trợ thuộc tính tạm thời của NSLayoutManager (thường dùng để tô màu các từ viết sai chính tả) không? Nếu vậy thì mục đích của cách đó là gì?
Bài viết thật xuất sắc (và với cá nhân tôi thì còn rất đúng lúc nữa, vì hiện tại tôi đang làm việc với NSTextViews). Bạn đã học được những thông tin này bằng cách nào? Từ code của người khác? Từ những trải nghiệm đau thương? Hay từ developer.apple.com?
Trong thời đại của các tài liệu DOM (ví dụ: notion, gitbook), tôi thường dùng attributed strings để làm những việc gần như ma thuật trong phân tích và thao tác văn bản. Đây là một cấu trúc rất thanh lịch, và tôi không hiểu vì sao nó lại ít được biết đến như vậy. Nhân tiện thì bài viết thật tuyệt vời.
Trước đây tôi từng thử tự viết trình soạn thảo văn bản của riêng mình từ đầu, và nếu khi đó có tài liệu như thế này thì đã tuyệt vời biết bao.
Vì tôi đã làm lập trình viên ứng dụng Android trong thời gian dài, nên thật thú vị khi thấy Apple tiếp cận vấn đề theo cách hơi khác và cẩn trọng hơn. Trên Android, lớp Layout (và các lớp con của nó) xử lý mọi thứ liên quan đến layout và rendering, còn TextView triển khai một phần logic chỉnh sửa/chọn lựa. Khác biệt duy nhất giữa EditText và TextView là EditText “bật” các tính năng chỉnh sửa vốn đã có sẵn trong TextView. Vấn đề với kiểu tiếp cận khá nguyên khối này (cùng với API tệ) là nếu ứng dụng cần nhiều quyền kiểm soát hơn đối với cách văn bản được render thì bạn gần như bó tay. Ví dụ, muốn truy cập từng glyph riêng lẻ sau khi layout xong à? Không đâu, xin lỗi nhé.
Ứng dụng TextEdit gần như được cấu thành hoàn toàn từ một TextView duy nhất. Tôi nghĩ WordPad là đối ứng phía Windows của nó. Nó dựa trên RichEdit control. Một sự thật thú vị khác là RTF về cơ bản là dạng tuần tự hóa của NSAttributedString. Điều tương tự cũng đúng với RichEdit control của Windows. Thực ra có vẻ như phía Windows đã có triển khai này trước: https://en.wikipedia.org/wiki/Rich_Text_Format
Tôi thật sự rất thích ứng dụng này. Nó đã thay thế mọi ứng dụng markdown khác của tôi, kể cả obsidian và ia Writer!
Thật may là ít nhất vẫn còn ai đó dùng Cocoa vào năm 2024.
Ước gì có nhiều tài liệu kiểu này hơn về các component của iOS!