1 điểm bởi GN⁺ 6 giờ trước | Chưa có bình luận nào. | Chia sẻ qua WhatsApp
  • Một giao thức terminal mới xuất hiện để giải quyết vấn đề cũ là muốn render icon tùy biến trong ứng dụng terminal thì phải cài font đã được vá (như Nerd Font)
  • Glyph Protocol cho phép ứng dụng đăng ký trực tiếp vector glyph vào terminal khi runtime và truy vấn xem một codepoint cụ thể có thể được render hay không
  • Dữ liệu glyph sử dụng định dạng glyf của TrueType, nhờ đó có thể tận dụng nguyên bộ rasterizer mà terminal đã có sẵn, triển khai mà không cần thêm dependency mới
  • Chỉ cho phép đăng ký codepoint trong Unicode Private Use Area (PUA) để chặn từ gốc các cuộc tấn công phishing và giả mạo thị giác
  • Rio terminal đang triển khai phiên bản đầu tiên, và mã ví dụ cho các framework TUI lớn như Bubble Tea, Ratatui, Ink đã được công bố

Vấn đề hiện tại: phụ thuộc vào font đã vá

  • Để editor, prompt và TUI trong terminal hiển thị icon đúng cách, người dùng phải tự cài các font đã vá như Nerd Font hoặc Powerline
  • Nếu không cài font, vị trí icon sẽ hiện tofu (□), trong khi các font đã vá thường khá nặng, khoảng 6~12MB mỗi font
    • JetBrainsMono Nerd Font Regular khoảng 7.8MB, FiraCode Nerd Font Regular khoảng 10.4MB, toàn bộ kho biểu tượng khoảng 60MB
  • Nhà phát triển ứng dụng không có cách nào tự phân phối glyph mình muốn; họ chỉ có thể trông chờ người dùng đã có đúng font, đúng phiên bản và đúng mapping codepoint

Các tính năng cốt lõi của Glyph Protocol

  • Hỗ trợ hai thao tác cốt lõi
    • Đăng ký glyph tùy biến: ứng dụng chọn một codepoint Unicode PUA và gửi trực tiếp vector outline vào terminal để đăng ký khi runtime
    • Truy vấn codepoint: hỏi xem một codepoint cụ thể được bao phủ bởi font hệ thống, bởi glyph đã đăng ký trong phiên, bởi cả hai, hay không bởi cái nào
  • Nếu người dùng đã cài Nerd Font, ứng dụng có thể dùng truy vấn để bỏ qua việc gửi glyph; còn nếu chưa cài thì vẫn có thể gửi trực tiếp outline để icon hiển thị bình thường

Cấu trúc giao thức

Cơ chế truyền (Transport)

  • Dùng APC (Application Program Command) thay vì OSC
  • APC được thiết kế cho các lệnh do ứng dụng định nghĩa, và các terminal chưa hỗ trợ có thể bỏ qua chuỗi này một cách an toàn
  • OSC dùng namespace toàn cục với một số nguyên thập phân duy nhất làm định danh lệnh nên có nguy cơ xung đột, còn APC có cấu trúc định danh riêng nên không gặp vấn đề này

Định danh (Identifier)

  • Mọi thông điệp Glyph Protocol đều có tiền tố là codepoint 25a1 (U+25A1, WHITE SQUARE)
    • Đây là ký hiệu tofu tiêu chuẩn mà terminal vẽ khi thiếu glyph
  • Định dạng framing: ESC _ 25a1 ; <verb> [ ; key=value ]* [ ; <payload> ] ESC \\
  • Có 4 verb: s(support), q(query), r(register), c(clear)

Support (s): kiểm tra terminal có hỗ trợ hay không

  • Dùng để xác định terminal hỗ trợ những định dạng payload và phiên bản giao thức nào
  • Đây cũng là cách chuẩn để phát hiện sự tồn tại của Glyph Protocol
  • fmt trong phản hồi là một bitfield, mỗi bit tương ứng với một định dạng payload
    • 1 = glyf: glyph đơn giản của TrueType, bắt buộc trong v1
    • 2 = colrv0: glyph màu phẳng nhiều lớp (OpenType COLR v0), thêm ở v1.2
    • 4 = colrv1: paint graph đầy đủ có gradient và transform (OpenType COLR v1), thêm ở v1.2
  • Có phản hồi nghĩa là giao thức được hỗ trợ; timeout nghĩa là không hỗ trợ; fmt=0 nghĩa là có triển khai giao thức nhưng không hỗ trợ định dạng nào cả (được định nghĩa để đảm bảo tính đầy đủ)

Query (q): truy vấn khả năng render của codepoint

  • Truy vấn xem một codepoint cụ thể có render được hay không và nhận về giá trị status
    • 0(free): không render gì cả, hiển thị tofu
    • 1(system): được font hệ thống bao phủ
    • 2(glossary): được glyph đã đăng ký trong phiên bao phủ
    • 3(both): được cả hai bao phủ, và glyph đã đăng ký sẽ ghi đè font hệ thống khi render
  • Nếu hệ thống đã có sẵn icon đó thì TUI có thể bỏ qua đăng ký; nếu không có thì đăng ký codepoint tùy biến để fallback một cách mượt mà

Register (r): đăng ký glyph

  • Ứng dụng chọn một codepoint PUA rồi gửi outline glyf được mã hóa base64 để đăng ký
  • Các tham số chính
    • cp: codepoint đích (hex), bắt buộc phải nằm trong 3 dải Unicode PUA (U+E000U+F8FF, U+F0000U+FFFFD, U+100000U+10FFFD); nếu nằm ngoài sẽ bị từ chối với reason=out_of_namespace
    • fmt: định dạng payload; trong v1 chỉ định nghĩa glyf và là mặc định nên thường có thể bỏ qua
    • upm: units per em, xác định không gian tọa độ của outline; mặc định là 1000
  • Gửi r lần thứ hai cho cùng một cp sẽ ghi đè đăng ký trước đó
  • Khi lỗi xảy ra (codepoint không thuộc PUA, payload sai, glyph composite, v.v.), phản hồi sẽ là status=<nonzero>; reason=<code>

Vì sao chọn định dạng glyf

Vì sao phải là vector

  • Glyph không phải ảnh nên không có độ phân giải cố định: cùng một icon cần render tốt cả trong TUI mật độ cao 12px lẫn trên màn hình HiDPI 24px
  • Glyph raster bị cố định ở một độ phân giải nhất định nên dễ mờ trên HiDPI hoặc khó đọc trong ô nhỏ

Vì sao chọn cụ thể glyf

  • Mọi terminal có thể render văn bản đều đã liên kết sẵn rasterizer glyf (FreeType, swash, ttf-parser, fontdue, allsorts, v.v.)
  • Khi áp dụng Glyph Protocol, phía terminal không cần thêm bất kỳ dependency mới nào
  • Nếu chọn SVG thì phải kéo theo resvg hoặc tự viết parser XML+path mới
  • Kích thước truyền cũng nhỏ hơn: một icon phổ biến chỉ cần 150~400 byte dữ liệu glyf, nhỏ hơn 2~3 lần so với SVG tương đương (kể cả overhead base64)
    • Khi đăng ký 50 icon, chênh lệch khoảng 13KB so với 35KB, đủ để thấy rõ trên pipe tmux hay kết nối SSH di động

Giải thích ngắn về glyf

  • Bản ghi glyf lưu glyph dưới dạng tập hợp các contour khép kín
  • Mỗi điểm mang 1 bit metadata là on-curve hoặc off-curve
    • Hai điểm on-curve liên tiếp → đường thẳng
    • Có điểm off-curve nằm giữa hai điểm on-curve → đường cong Bézier bậc hai
    • Hai điểm off-curve liên tiếp → tồn tại một điểm on-curve ngầm ở giữa (một mẹo nén dữ liệu)
  • Tọa độ là các vị trí nguyên trên lưới trong EM square; với upm=1000, (500, 900) nghĩa là nửa chiều rộng và 90% chiều cao
  • Một tam giác khép kín vào khoảng 30 byte, còn icon 30 điểm vào khoảng 200 byte

Tập con glyf do giao thức định nghĩa

  • Chỉ cho phép glyph đơn giản: không có glyph composite, không tham chiếu glyph khác, không có ngữ cảnh cấp font
  • Dùng mã hóa cờ tiêu chuẩn được định nghĩa trong đặc tả OpenType
  • Không có lệnh hinting: hinting giả định có bộ giá trị điều khiển cho toàn bộ font, điều này không phù hợp ở đây
  • Không gian tọa độ được xác định bằng upm, mặc định 1000, có thể override cho từng lần đăng ký

Màu sắc, scaling và khâu tạo glyph

  • Outline glyf không chứa thông tin màu và sẽ được render bằng màu foreground hiện tại → giống hệt trường hợp kế thừa từ Nerd Font
  • Glyph màu được hỗ trợ bằng các định dạng payload riêng fmt=colrv0 / fmt=colrv1
  • Giá trị upm xác định không gian tọa độ glyph, terminal sẽ ánh xạ nó vào ô khi render → không cần đăng ký lại khi đổi cỡ font
  • Hầu hết nhà phát triển sẽ không tự viết byte glyfchuyển đổi từ SVG ở bước build: có thể dùng interface ttx/pens của fonttools, và helper svg2glyf cũng dự kiến được phát hành cùng bản triển khai tham chiếu của Rio

Vòng đời và dung lượng

  • Mỗi phiên terminal có một glossary chứa tối đa 1024 glyph đăng ký đồng thời, được key bằng codepoint trong 3 dải PUA
  • Các đăng ký có hiệu lực trong suốt thời gian tồn tại của phiên
  • Khi đăng ký glyph thứ 1025, mục cũ nhất sẽ bị đẩy ra theo thứ tự FIFO → không có lỗi “glossary full”
  • Ứng dụng không thể chấp nhận việc bị đẩy ra trong im lặng thì phải truy vấn codepoint đó trước khi xuất ra

Ví dụ thực tế: đăng ký icon vào một PUA trống

  • Có ví dụ đầy đủ về pipeline đăng ký một outline được cách điệu vào U+100000 (codepoint đầu tiên của Supplementary PUA-B)
  • Dùng fontTools làm bộ chuyển đổi SVG→glyf
  • Vẽ outline bằng TTGlyphPen, sau đó mã hóa base64 và gửi qua chuỗi APC, rồi in ra codepoint tương ứng
  • Với icon thông thường khoảng 20 điểm, payload glyf chỉ khoảng 150 byte, còn nếu tính cả APC wrapping và base64 thì khoảng 250 byte
  • Với các nhà phát triển đã có sẵn tài nguyên SVG, helper svg2glyf dự kiến sẽ được cung cấp → hoàn tất đăng ký chỉ trong 2 dòng

Tùy chọn cho đăng ký hàng loạt: reply=

  • Theo mặc định terminal sẽ gửi phản hồi ACK cho mọi r, nhưng trong hook khởi động đăng ký 100 glyph thì 100 ACK bị xếp hàng chảy ra PTY và hiện thành rác trong shell
  • Có 3 mức điều khiển
    • reply=1 (mặc định): phản hồi cả thành công lẫn thất bại, dùng cho đăng ký đơn lẻ có tương tác
    • reply=2: chỉ phản hồi khi thất bại, còn thành công thì im lặng, dùng khi đăng ký hàng loạt mà chỉ cần phát hiện lỗi
    • reply=0: không phản hồi gì, fire-and-forget, dùng trong các hook khởi động nơi không có tiến trình nào đọc phản hồi
  • Giá trị không xác định sẽ tự động fallback về reply=1, nên vẫn giữ được khả năng tương thích ngược khi mở rộng sau này

Clear (c): hủy đăng ký

  • Dùng khi thoát editor để khôi phục mặc định của terminal, khi đổi theme TUI, hoặc để debug
  • Hủy một slot: chỉ định codepoint cụ thể bằng tham số cp
  • Xóa toàn bộ glossary: bỏ qua cp
  • Hủy một slot trống không phải lỗi mà là no-op, phản hồi status=0
  • cp phải nằm trong dải PUA; nếu nằm ngoài sẽ trả về reason=out_of_namespace

Những gì cố ý không đưa vào v1

  • Không thể đăng ký codepoint ngoài PUA: chỉ giới hạn trong 3 dải Unicode PUA
  • Không có ligature: đăng ký chỉ áp dụng cho một codepoint đơn; thay thế chuỗi khóa không thuộc phạm vi v1; các ligature lập trình (->) vốn đã do font OpenType xử lý
  • Không có tính bền vững giữa các phiên: mỗi lần chạy phải gửi lại glyph mới, để tránh biến terminal thành một bộ nhớ đệm font
  • Không có chia sẻ giữa các ứng dụng: mỗi phiên terminal sở hữu glossary riêng, không có IPC hay daemon
  • Payload glyf của v1 không có glyph màu: render bằng màu foreground; màu được tách riêng sang colrv0/colrv1 ở v1.2
  • Những tính năng này có thể được thêm sau nếu cần, nhưng một khi đã thêm thì rất khó gỡ bỏ, nên chủ ý loại khỏi v1

Cơ sở bảo mật của việc giới hạn PUA

  • Việc giới hạn vào PUA không phải vì tính thẩm mỹ của API mà là thuộc tính giúp giao thức vẫn an toàn khi bật mặc định
  • Nếu cho phép đăng ký codepoint tùy ý: có thể đăng ký một glyph hình o vào U+0061 (a) để bad.com trông như bod.com
    • Buffer ô vẫn là bad.com, nên copy-paste vẫn trung thực về byte, nhưng thứ người dùng nhìn thấy là sai
    • Điều đó tạo ra một primitive cho phishing cho mọi chương trình terminal, và ảnh hưởng còn kéo dài sang các chương trình chạy sau trong cùng phiên
  • Nếu giới hạn vào PUA thì kiểu tấn công này bị loại bỏ một cách cơ học: người dùng không gõ codepoint PUA, và tên file, URL, lệnh, tên biến hay log cũng không chứa codepoint PUA
  • Mô hình tin cậy mà Nerd Font đã hình thành theo thông lệ — glyph tùy biến chỉ tồn tại trong vùng dành riêng, không thể nằm đè lên văn bản thật — được ép buộc ở cấp độ giao thức
  • Các thuộc tính bảo mật bổ sung
    • Buffer ô là nguồn chân lý: chọn, sao chép, tìm kiếm, phát hiện hyperlink, shell history, v.v. đều phải trả về codepoint mà ứng dụng đã xuất ra, nên không thể tạo bẫy “thấy một đằng, copy một nẻo”
    • Cô lập theo phiên: hai tab có thể đăng ký hai icon branch khác nhau cho U+E0A0, và đăng ký của tab này không ảnh hưởng tới render của tab kia

So sánh với các cách tiếp cận hiện có

Kitty Image Protocol (KIP) + Unicode Placeholders

  • Có thể xấp xỉ Glyph Protocol bằng Unicode placeholder của KIP, nhưng tích hợp khá phức tạp và các terminal hỗ trợ placeholder hiện chỉ có Kitty, Ghostty và Rio
  • KIP là một giao thức hình ảnh, trong khi glyph không phải hình ảnh
    • Chi phí theo từng lần sử dụng: nếu một glyph được tái sử dụng 200 lần trên màn hình (ví dụ viền bảng, bullet marker, v.v.) thì phải đặt 200 tham chiếu hình ảnh, kéo theo chi phí layout và compositing. Với Glyph Protocol, sau khi đăng ký codepoint thì glyph sẽ được render ở tốc độ của font
    • Không có độ phân giải native: outline glyf không gắn với kích thước pixel nên tự thích nghi khi đổi cỡ font. KIP gửi bitmap ở một kích thước cụ thể nên khi đổi cỡ sẽ mờ hoặc phải upload lại, mà cũng không có cơ chế phát hiện thay đổi cỡ font
    • Kế thừa màu foreground: outline glyf đơn sắc được render bằng màu foreground hiện tại của ô nên tự động theo theme. Hình ảnh thì có pixel riêng nên không tham gia vào việc tô màu văn bản

DEC DECDLD / DRCS

  • Đây là Dynamically Redefinable Character Sets do VT220 giới thiệu từ năm 1983, về hình thức khá giống Glyph Protocol
  • Có hai vấn đề cốt lõi
    • Cách làm bằng bitmap: vì upload lưới pixel khớp với kích thước ô hiện tại của terminal, nên khi đổi cỡ font, chuyển sang HiDPI hay màn hình 4K, các pixel khối sẽ bị phóng to hoặc thu nhỏ. Cách này phù hợp với thời CRT cố định 10×20 chứ không hợp với kích thước ô đa dạng hiện đại
    • Không giới hạn namespace: DECDLD có thể ghi đè những bộ ký tự ánh xạ vào vùng GL (nơi có a, b, c), nghĩa là chương trình không đáng tin cậy có thể định nghĩa lại cách render của a → đây là lý do lớn nhất khiến terminal hiện đại không muốn bật DECDLD

Tình trạng triển khai trong Rio terminal

  • Glyph Protocol đã dùng được trên nhánh main của Rio terminal và dự kiến chính thức được đưa vào trong tháng 5 → bản triển khai đầu tiên
  • Toàn bộ đặc tả sẽ được công bố cùng bản phát hành, kèm mã ví dụ để đăng ký glyph và truy vấn terminal
  • Có thể xem ví dụ hoạt động trong kho raphamorim/glyph-protocol-examples: bao gồm các mẫu tích hợp cho Bubble Tea, Ratatui và Ink
  • Giao thức vẫn còn có thể thay đổi; khi nhiều ứng dụng và terminal khác tham gia hơn, hình thức thông điệp, phản hồi truy vấn và các edge case có thể được điều chỉnh → hiện nên coi đây là mục tiêu di động khi build và nên cố định phiên bản triển khai
  • Tác giả kỳ vọng các terminal emulator khác sẽ áp dụng, vì lợi ích cho toàn bộ hệ sinh thái là rất lớn trong khi phạm vi triển khai được cố ý giữ nhỏ

Câu hỏi mở cho cộng đồng

  • Thông báo thay đổi cỡ font có nên nằm trong phạm vi giao thức hay không?: bản thân Glyph Protocol tránh được vấn đề này vì outline độc lập độ phân giải, nhưng các TUI kết hợp cả hình ảnh lẫn glyph thì không có cách nào biết cell metric thay đổi ngoài polling → đang có thảo luận về việc resize hoặc thông báo metrics-changed có nên thuộc phạm vi hay là vượt quá phạm vi
  • Có cách có trách nhiệm nào để cho phép đăng ký ngoài PUA không?: quy tắc chỉ dùng PUA đảm bảo an toàn mặc định, nhưng lại chặn các trường hợp như bộ gõ CJK gửi glyph cho Hán tự chưa được cover hoặc công cụ theo ngôn ngữ muốn override glyph → đang xin ý kiến về các cơ chế như opt-in rõ ràng ở cấp người dùng, tính năng có chữ ký, cờ nguồn tin cậy, v.v. để mở ra các trường hợp này mà không tái tạo nguy cơ phishing

Chưa có bình luận nào.

Chưa có bình luận nào.