Trời tính không bằng máy tính

blog, khám phá

Từ dòng code tới màn hình (phần 2)

Computer font

Mỗi ngày, bạn đều nhìn thấy xung quanh có rất nhiều chữ đánh máy: chữ trên màn hình, trên biển báo, biển số xe, bảng hiệu quán ăn,… Đã bao giờ, bạn tự hỏi, làm thế nào mà máy tính hiển thị được đủ loại font khác nhau, đủ kiểu khác nhau?

Ở bài viết số 2 trong loạt bài “Từ dòng code đến màn hình”, chúng ta sẽ tìm hiểu 2 thứ mà chúng ta dùng hằng ngày nhưng ít khi để ý: font chữ kỹ thuật số (computer font).

Nếu muốn đọc các số khác trong loạt bài “Từ dòng code tới màn hình”, các bạn hãy bấm vào đây nhé

Ngày xửa ngày xưa

Từ thời xa xưa, những máy vi tính đầu tiên trên thế giới thực ra không có màn hình, mà chúng gửi những ký tự muốn hiển thị qua một cái máy điện tín / máy đánh chữ cơ học (cũng vì vậy mà function để hiện chữ ra màn hình có tên là “print”). Sau đây là hình ảnh một máy đánh chữ như vậy, đã được phục chế để hiển thị dòng lệnh Linux (Debian):

Dễ có thể thấy, font ở máy đánh chữ dạng này là những “con dấu” được đúc cứng bằng thép, và gần như không có cách nào để thay đổi font cả (trừ việc mua cái máy in mới).

Bitmap font

Khi máy tính bắt đầu chuyển sang dùng màn hình thay thế máy in, các font chữ sơ khai được lưu trực tiếp ở bộ nhớ ROM của terminal, chỉ có nhà sản xuất mới có thể thay đổi. Font dạng này được gọi là Bitmap font, vì mỗi con chữ của font được lưu dưới dạng một hình ảnh (bitmap):

Dễ có thể thấy, cách này giúp việc xử lý font chữ rất đơn giản, cũng như nó tốn ít dung lượng để lưu trữ. Thế nhưng, điểm trừ lớn nhất đó là font dạng này không thể phóng to được. Giống như kiểu ảnh pixel phóng to thì sẽ bị “vỡ ảnh”, font muốn phóng to được thì phải lưu dưới dạng vector.

Vector font / outline font

Như tên gọi, dạng font này lưu mỗi ký tự dưới dạng vector. Một trong những định dạng ra đời sớm nhất hỗ trợ vector, đó là TrueType, phát triển bởi Adobe vào những năm 1980. Định dạng TrueType vẫn được sử dụng rất phổ biến tới tận năm 2023 này.

Định dạng TrueType (và sau này có thêm OpenType) hoạt động chủ yếu dựa trên công thức đường cong Bézier trong toán học. Về chủ đề toán học thì mình xin được phép “biến mất” vì mình khá dốt toán, nhưng đại khái thuật toán Bézier giúp việc chuyển từ vector sang pixel một cách dễ dàng (thuật toán nhẹ, có thể chạy nhanh). Ngoài ra nó cũng không khó để điều chỉnh đối với người không làm về lập trình (nếu bạn từng dùng Pen Tool trong Adobe Illustrator, thì nó chính là Bézier đó!)

Quá trình chuyển đổi từ vector sang pixel được gọi là “rasterize”. Cách nó hoạt động, đó là nó sẽ tính toán xem pixel nào giao với đường cong, thì pixel đó sẽ được “bật lên”. Ví dụ sau đây, phần màu xanh biển sẽ là những pixel được bật lên sau quá trình rasterize:

Áp dụng phương pháp này, ta đã có thể thay đổi cỡ chữ to nhỏ tùy ý. Đây cũng chính là phương pháp được sử dụng ở những phiên bản mac và windows đời đầu:

Thế nhưng, khi nhìn vào góc cạnh của chữ, chúng ta dễ dàng để ý là nó khá nham nhở (bị răng cưa). Lý do vì pixel bây giờ chỉ có trạng thái “bật” và “tắt”, chưa có trạng thái 20%, 50%,… Hạn chế này đến từ 2 lý do, (1) là vì màn hình máy tính đời đầu chưa hiện được nhiều màu sắc, và (2) là giới hạn về tốc độ xử lý.

Để khắc phục, ta có thể xét xem đường cong có cách xa điểm chính giữa của pixel nhiều hay không, sau đó điều chỉnh độ đậm nhạt của pixel tùy theo khoảng cách này. Phương pháp này còn được gọi là “font smoothing”, ảnh sau đây (bên trái) là ví dụ về cách hoạt động của thuật toán này, còn ảnh bên phải là cũng chữ phía trên nhưng có bật font smoothing:

Lưu ý thêm, rằng thuật toán font smoothing đã “tiến hóa” và trở nên hoàn chỉnh hơn nhiều. Ví dụ, ở windows XP, tính năng ClearType lợi dụng subpixel (từng đèn màu RGB riêng biệt trong một pixel) để làm chữ hiện mượt hơn trên màn hình LCD. Ở ảnh sau đây, ảnh (a) là không font smoothing, còn ảnh (b) là dùng ClearType:

Ngoài font smoothing, người ta còn thường áp dụng font hinting để làm chữ hiện rõ nét hơn. Phương pháp này cố gắng “nhồi” vector vào pixel gần nhất, kết quả là chữ hiện ra sẽ đỡ bị “nhòe” hơn. Font hinting đặc biệt cần thiết với hệ chữ viết có ký tự phức tạp. Ảnh sau đây là đoạn chữ Hán (khá nhiều nét), với font hinting tắt (bên trái) và bật (bên phải). Hãy để ý các nét kẻ ngang nhìn thanh mảnh và rõ nét hơn hẳn:

Phía hệ điều hành

Tuy việc rasterize một ký tự là khá nhanh, nhưng nếu mỗi khi cần hiện một ký tự mà nó phải rasterize lại thì sẽ “tích tiểu thành đại”, máy tính sẽ giật lag liên tục. Để giải quyết vấn đề này, font sẽ được rasterize ít nhất có thể, sau đó các phần mềm sẽ sử dụng phiên bản pixel đã lưu đệm (cache) sau quá trình rasterize. Tên gọi của phương pháp này là Glyph Cache

Việc còn lại là tự động xuống dòng. Hãy để ý xem, các văn bản mà bạn đọc trên màn hình đều tự động xuống dòng ở dấu cách, chứ không mấy khi nó c-ắt ch-ữ r-a nh-ư t-hế nà-y. Phương pháp này có tên là Line wrap and word wrap, và cách hoạt động thì đơn giản là tính tổng chiều rộng của từng ký tự sẽ hiển thị, thấy nó lớn hơn khung hiển thị thì tìm dấu cách cuối cùng và biến nó thành “xuống dòng”, sau đó lặp lại.

Tất cả những phương pháp và thuật toán kể trên khá phức tạp, nhưng thực tế nó đã được code sẵn trong hệ điều hành hoặc trong các thư viện, ví dụ như ở Linux thì có libpango đảm nhiệm việc này. Lập trình viên chỉ đơn giản là viết code xem muốn hiện chữ gì, font gì, size bao nhiêu,…

Lời kết

Vậy là chúng ta đã “bóc tách” được thêm một sự thật “hiển nhiên” mà hằng ngày ta đều quan sát thấy. Mình hy vọng các bạn tìm thấy một chút gì đó hữu ích từ bài viết này. Rất cảm ơn các bạn đã dành thời gian theo dõi.

Ở bài viết tiếp theo, mình sẽ nói về graphical shell. Hẹn sớm gặp lại các bạn!

Tham khảo

MỚI! Đăng ký nhận bài viết mới nhất qua chatbot: