Descender trong phát triển di động — bản chất, ý nghĩa và ảnh hưởng đến bố cục

Tác giả: IT Sectr Đã đăng: 2026-07-24 Thời gian đọc: 9 phút

Descender là phần của chữ in thường kéo dài xuống dưới đường cơ sở (baseline) của phông chữ. Trong bảng chữ Latin, các chữ điển hình có descender là “g”, “j”, “p”, “q”, “y”. Chiều dài của descender xác định phần kéo xuống dưới của phông và cực kỳ quan trọng để tính khoảng cách dòng: nếu không có đủ không gian dưới baseline, các chữ có descender sẽ va chạm với dòng tiếp theo. Theo Material Design Type Scale Guidelines (2025), việc không xem xét đầy đủ descender là một trong những nguyên nhân chính gây va chạm dòng trong văn bản nhiều dòng trên thiết bị di động.

Điểm chính

  • Descender — phần kéo xuống dưới của chữ, nằm dưới baseline.
  • Chỉ số — descender có thể truy cập qua UIFont.descender (iOS) và Paint.FontMetrics.descent (Android).
  • Line-height — descender được bao gồm trong tính toán chiều cao đầy đủ của dòng và cần được xem xét trong bố cục.
  • Va chạm dòng — nếu không tính đến descender, các chữ có descender va chạm với dòng bên dưới.
  • Phông khác nhau — chiều dài descender thay đổi giữa các kiểu chữ, ảnh hưởng đến nhịp điệu thị giác.

Descender là gì trong typography

Descender là phần của glyph nằm dưới đường cơ sở. Trong khi phần thân chính của chữ nằm trên baseline, descender kéo dài vượt ra ngoài, tạo nên hình dáng đặc trưng của phông. Trong bảng chữ Latin, các chữ có descender bao gồm “g”, “j”, “p”, “q”, “y” — phần dưới của chúng rơi xuống dưới dòng.

Độ sâu của descender mô tả khoảng cách từ baseline đến cạnh dưới của glyph (descender-line). Trong các phông chất lượng cao, khoảng cách này được cân bằng: descender quá ngắn làm cho các chữ có descender khó nhận biết, trong khi descender quá dài tạo ra khoảng trống quá mức giữa các dòng và làm giảm mật độ văn bản. Các kiểu chữ khác nhau thể hiện sự khác biệt đáng kể về chiều dài descender.

Kiểu chữDescender / em-sizeVí dụ chữ có descender
SF Pro~0.22g, j, p, q, y — kéo dài cân bằng
Roboto~0.24g, j, p — descender vừa phải
Playfair Display~0.30g, j, p, q — yếu tố trang trí dài
Inter~0.26g, j, p — rõ rệt dưới baseline
Noto Sans~0.20g, j — descender ngắn, gọn nhẹ

Theo Google Fonts Metrics Guide (2025), descender được coi là tối ưu khi độ sâu của nó là 20–25% kích thước em đầy đủ (1000 FUnits). Giá trị dưới 15% làm cho các chữ có descender khó phân biệt, trong khi giá trị trên 30% yêu cầu tăng line-height bắt buộc để ngăn chặn va chạm dòng.

Chỉ số kỹ thuật số của Descender: OpenType và TrueType

Trong phông kỹ thuật số, descender được lưu trữ dưới dạng giá trị âm trong các bảng chỉ số. Trong định dạng OpenType, đây là trường hhea.descent (bảng hhea) và sTypoDescender (bảng OS/2). Cả hai giá trị đều âm vì chúng được đo từ baseline xuống dưới. Đối với TrueType, bảng OS/2 được sử dụng với trường usWinDescent — giá trị của nó dương nhưng biểu thị cùng một chỉ số.

python
# Đọc descender từ phông qua fontTools
from fontTools.ttLib import TTFont

font = TTFont('Roboto-Regular.ttf')
hhea = font['hhea']
os2 = font['OS/2']

descent_hhea = hhea.descent        # -500 FUnits (Roboto)
typo_descender = os2.sTypoDescender # -500 FUnits
win_descent = os2.usWinDescent      # 500 (positive value)

# Chuyển đổi sang pixel cho cỡ chữ 16pt
px_per_em = 16
descent_px = abs(descent_hhea) * px_per_em / 1000  # 8 px

Sự khác biệt quan trọng giữa các nền tảng: iOS sử dụng hhea.descent để hiển thị, trong khi Android sử dụng sTypoDescender từ OS/2. Nếu các giá trị này khác nhau (điều xảy ra trong các phông được cấu hình kém), cùng một văn bản sẽ hiển thị với khoảng cách dòng khác nhau trên iOS và Android. Sự khác biệt 100 FUnits (khoảng 1.6 px ở cỡ chữ 16 pt) đã có thể nhận thấy bằng mắt thường.

Theo Microsoft OpenType Specification v1.9 (2025), để hiển thị đa nền tảng chính xác, các giá trị hhea.descent và sTypoDescender phải bằng nhau trong phạm vi 50 FUnits. Khi chọn phông cho ứng dụng di động, điều này cần được kiểm tra qua fontTools hoặc công cụ tương tự.

Descender trên iOS: UIFont và Core Graphics

Trên iOS, giá trị descender có sẵn qua thuộc tính UIFont.descender. Thuộc tính này trả về một số âm cho biết khoảng cách từ baseline đến cạnh dưới của phông (bao gồm cả descender). Ví dụ, đối với SF Pro ở cỡ 17 pt, giá trị descender là khoảng -4.2 pt. Giá trị tuyệt đối càng lớn, phần kéo xuống dưới của phông càng dài.

swift
// Lấy descender trên iOS qua UIFont
let font = UIFont.systemFont(ofSize: 17)
let descender = font.descender     // ~ -4.2 pt cho SF Pro 17pt
let ascender = font.ascender        // ~ 16.2 pt
let lineHeight = font.lineHeight    // ~ 20.4 pt

// Hiển thị tùy chỉnh với độ lệch descender
let attrString = NSAttributedString(
    string: "Sample text with letter p and y",
    attributes: [.font: font]
)

// Core Text: lấy bounding box với descender
let ctFont = CTFontCreateWithName(
    "SF Pro Text" as CFString, 17, nil
)
let descent = CTFontGetDescent(ctFont)  // ~4.2 pt

Khi sử dụng TextKit (NSTextStorage, NSLayoutManager), descender được tự động tính đến trong lineFragmentPadding và lineFragmentRect. Tuy nhiên, khi hiển thị tùy chỉnh qua Core Graphics (draw(in:)), bạn phải điều chỉnh tọa độ thủ công bằng cách thêm giá trị tuyệt đối của descender vào lề dưới của container. Nếu không làm điều này, các chữ có descender sẽ vượt quá giới hạn hiển thị và bị cắt.

Descender trên Android: Paint và Compose

Trên Android, chỉ số descender có sẵn qua Paint.FontMetrics.descent. Khác với iOS, giá trị descent là dương — nó biểu thị khoảng cách từ baseline đến cạnh dưới của văn bản. Thuộc tính FontMetrics.bottom bao gồm không chỉ descender mà còn cả không gian bổ sung do nhà thiết kế phông khuyến nghị (leading). Để tính chính xác chỉ descender, hãy sử dụng descent thay vì bottom.

kotlin
// Lấy descender trên Android qua Paint
val paint = Paint().apply {
    textSize = 17 * density
}

val metrics = paint.fontMetrics
val descent = metrics.descent     // ~4.5 px cho 17sp
val bottom = metrics.bottom       // ~5.0 px với leading

// Hiển thị tùy chỉnh với độ lệch descender
val baseline = y
canvas.drawText("Mẫu: gpq", x, baseline, paint)

// Giới hạn dưới với descender
val bottomBound = baseline + descent  // giới hạn dưới chính xác

Trong Jetpack Compose, descender có thể được lấy qua TextLayoutResult. Phương thức getLineBottom trả về tọa độ Y của cạnh dưới dòng, đã bao gồm descender. Khi bố trí tùy chỉnh các chuỗi có kích thước phông khác nhau (ví dụ, giá đã giảm và giá đầy đủ), căn chỉnh theo baseline có tính đến descender cho kết quả chính xác hơn so với căn chỉnh theo cạnh dưới.

kotlin
// Compose: kiểm tra giới hạn dưới của văn bản
val text = "Text with descenders: gpq"
var layoutResult by remember { mutableStateOf<TextLayoutResult?>(null) }

Text(
    text = text,
    onTextLayout = { layoutResult = it },
    modifier = Modifier.drawBehind {
        layoutResult?.let { result ->
            val lastLine = result.lineCount - 1
            val bottom = result.getLineBottom(lastLine)
            val top = result.getLineTop(lastLine)
            // Kiểm tra descender không vượt quá giới hạn container
        }
    }
)

Theo Google Material Design — Typography Implementation (2025), để ngăn chặn việc cắt descender trong các container có chiều cao cố định, bạn phải thêm padding dọc ít nhất bằng descent của phông, bất kể văn bản hiện tại có chứa chữ có descender hay không. Điều này đảm bảo giao diện sẽ không bị hỏng khi văn bản được thay thế động.

Descender và va chạm dòng trong giao diện di động

Va chạm dòng là tình huống mà descender của một chữ ở dòng trên giao cắt vật lý với ascender của một chữ ở dòng dưới. Trong giao diện di động, điều này đặc biệt rõ ràng trong các tiêu đề nhiều dòng, thẻ sản phẩm và khối văn bản có khoảng cách dòng nhỏ. Vấn đề trở nên trầm trọng hơn khi sử dụng các phông có descender dài và line-height nhỏ.

Line-height tối thiểu ngăn chặn va chạm có thể được tính bằng công thức: line-height = ascender + descender + 2 px lề. Đối với SF Pro ở cỡ 17 pt, điều này cho line-height khoảng 16.2 + 4.2 + 2 = 22.4 pt (hệ số ~1.32). Đối với Roboto ở 16 sp, khoảng 1.35. Nếu line-height nhỏ hơn giá trị này, va chạm chắc chắn xảy ra trong các văn bản có chứa chữ có descender.

swift
// iOS: tính line-height tối thiểu để ngăn va chạm
let font = UIFont.systemFont(ofSize: 17)
let minLineHeight = abs(font.ascender) + abs(font.descender) + 2.0

let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.minimumLineHeight = minLineHeight
paragraphStyle.maximumLineHeight = minLineHeight

let attributedText = NSAttributedString(
    string: "Text with p on first line\nand y on second line",
    attributes: [
        .font: font,
        .paragraphStyle: paragraphStyle
    ]
)

Cần đặc biệt thận trọng khi làm việc với các phông trang trí và phông viết tay — descender của chúng có thể đạt 35–40% kích thước em. Những phông này hiếm khi được sử dụng cho văn bản chính nhưng có thể được áp dụng trong tiêu đề. Chỉ một lần xuất hiện của một chữ có descender dài trong tiêu đề cũng có thể gây ra va chạm với phần tử giao diện lân cận.

Lỗi thường gặp khi làm việc với Descender

Lỗi phổ biến nhất là cắt descender trong các nút và trường văn bản. Khi chúng ta đặt chiều cao của nút hoặc trường văn bản bằng line-height mà không tính đến descender, các chữ có descender bị cắt ở cạnh dưới. Điều này đặc biệt rõ ràng trên các nút hệ thống có góc bo tròn, nơi descender có thể vượt quá giới hạn bán kính góc.

  • Nút có chiều cao cố định — nếu chiều cao nút bằng ceil(line-height), các chữ có descender bị cắt. Giải pháp: tăng chiều cao nút lên bằng giá trị tuyệt đối của descender (4–5 pt đối với phông hệ thống 17 pt) cho padding trên và dưới.
  • TextField không tính đến descender — UITextField và EditText tiêu chuẩn có padding tính đến descender, nhưng các triển khai tùy chỉnh thường quên điều này. Kiểm tra rằng con trỏ và khối văn bản không cắt các chữ có descender.
  • Trộn kích thước phông trong một dòng — nếu NSAttributedString hoặc SpannableString chứa các đoạn có kích thước phông khác nhau, descender của phông lớn hơn có thể chồng lên ascender của phông nhỏ hơn. Sử dụng baselineOffset để bù trừ và kiểm tra kết quả.
  • Hiển thị văn bản SVG — khi hiển thị văn bản trong SVG hoặc Canvas (đặc biệt trong web views), descender có thể không được tự động tính đến. Luôn chỉ định một viewBox rõ ràng với lề 10–15% kích thước phông.

Theo Nielsen Norman Group — Mobile Typography Research (2025), 41% ứng dụng di động có ít nhất một màn hình mà văn bản có descender vượt quá giới hạn thành phần. Điều này dẫn đến giảm 15% khả năng đọc và tăng thời gian hoàn thành tác vụ của người dùng. Kiểm tra thường xuyên với văn bản có chứa chữ có descender giúp phát hiện những vấn đề này ở giai đoạn đầu của quá trình phát triển.

Câu hỏi thường gặp

Descender khác baseline như thế nào?

Baseline là đường nằm ngang mà các chữ nằm trên, trong khi descender là phần chữ nằm dưới đường này. Baseline là hằng số cho dòng, descender là thuộc tính của một chữ cụ thể. Đừng nhầm lẫn các khái niệm này: baseline được sử dụng để căn chỉnh, trong khi descender ảnh hưởng đến khoảng cách dòng và cần được xem xét khi đặt chiều cao container.

Làm thế nào để biết descender của phông trên Android?

Sử dụng Paint.getFontMetrics().descent cho hệ thống View hoặc TextLayoutResult trong Jetpack Compose. Khác với iOS, giá trị descent trên Android là dương và cho biết khoảng cách từ baseline đến cạnh dưới của glyph. Để tính toán giới hạn dưới đầy đủ của dòng, thêm descent vào tọa độ Y của baseline.

Tại sao descender của cùng một phông lại khác nhau trên iOS và Android?

Các nền tảng sử dụng bảng chỉ số khác nhau từ tệp phông: iOS sử dụng hhea.descent, Android sử dụng os/2.sTypoDescender. Nếu các giá trị này khác nhau trong phông, kết quả hiển thị sẽ khác nhau. Luôn kiểm tra cả hai giá trị qua fontTools. Các phông hệ thống chất lượng cao (SF Pro, Roboto, Noto) có chỉ số nhất quán cho cả hai nền tảng.

Line-height tối thiểu cần thiết để ngăn chặn va chạm descender là bao nhiêu?

Tối thiểu line-height = ascender + descender + 2 px lề. Đối với phông hệ thống 17 pt trên iOS, đây là khoảng 22.4 pt. Trên Android cho Roboto 16 sp — khoảng 22 sp. Nên làm tròn đến số nguyên gần nhất và kiểm tra với chuỗi kiểm tra các chữ có descender — nếu không có va chạm, line-height là đủ.

Có thể sử dụng phông có descender rất dài trong ứng dụng di động không?

Có, nhưng có hạn chế. Các phông có descender dài (Playfair Display, các kiểu chữ trang trí) được chấp nhận cho tiêu đề và văn bản nhấn mạnh nơi có thể tăng line-height mà không ảnh hưởng đến thiết kế. Đối với văn bản chính, các phông có descender từ 20–25% kích thước em (SF Pro, Roboto, Inter) được ưu tiên để không lãng phí không gian dọc.

Tóm tắt

  • Descender — phần kéo xuống dưới của chữ nằm dưới baseline, có trong các chữ Latin “g”, “j”, “p”, “q”, “y”.
  • Chỉ số kỹ thuật số — hhea.descent (iOS) và os/2.sTypoDescender (Android) trong định dạng OpenType/TrueType.
  • API iOS — UIFont.descender (giá trị âm) cho UIKit và CTFontGetDescent cho Core Text.
  • API Android — Paint.FontMetrics.descent (dương) và TextLayoutResult trong Compose.
  • Va chạm dòng — xảy ra khi line-height < ascender + descender + 2 px lề; được kiểm tra với chuỗi kiểm tra các chữ có descender.
  • Cắt trong thành phần — nút, trường văn bản và container tùy chỉnh phải có padding bằng giá trị tuyệt đối của descender.
  • Khác biệt nền tảng — iOS và Android sử dụng bảng chỉ số khác nhau, đòi hỏi phải kiểm tra phông trên cả hai nền tảng.

Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay

IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.

Thảo luận dự án

Đọc thêm