Descender في التطوير المحمول — الجوهر والمعنى والتأثير على التخطيط

المؤلف: IT Sectr نُشر: 2026-07-24 وقت القراءة: 9 دق

Descender هو جزء من الحرف الصغير يمتد تحت خط الأساس (baseline) للخط. في السيريلية، الأحرف النموذجية ذات descender هي «er» و«u» و«ef»، وفي اللاتينية — «g» و«j» و«p» و«q» و«y». يحدد طول descender العنصر السفلي الممتد للخط وهو مهم بشكل حاسم لحساب تباعد الأسطر: بدون مساحة كافية أسفل baseline، ستصطدم الأحرف ذات descender بالسطر التالي. وفقاً لإرشادات Material Design Type Scale Guidelines (2025)، فإن عدم مراعاة descender بشكل كافٍ هو أحد الأسباب الرئيسية لتصادم الأسطر في النص متعدد الأسطر على الأجهزة المحمولة.

النقاط الرئيسية

  • Descender — العنصر السفلي الممتد للحرف الموجود أسفل baseline.
  • المقياس — يمكن الوصول إلى descender عبر UIFont.descender (iOS) وPaint.FontMetrics.descent (Android).
  • Line-height — يتم تضمين descender في حساب الارتفاع الكلي للسطر ويتطلب مراعاة في التخطيط.
  • تصادم الأسطر — بدون مراعاة descender، تصطدم الأحرف ذات descender بالسطر السفلي.
  • الخطوط المختلفة — يختلف طول descender بين أنواع الخطوط، مما يؤثر على الإيقاع البصري.

ما هو Descender في الطباعة

Descender هو جزء من الرسم الحرفي (glyph) الموجود أسفل خط الأساس. بينما يجلس الجسم الرئيسي للحرف على baseline، يمتد descender إلى ما وراءها، مما يخلق الشكل المميز للخط. في الأبجدية اللاتينية، تشمل الأحرف ذات descender «g» و«j» و«p» و«q» و«y» — حيث تنخفض عناصرها السفلية أسفل السطر.

يصف عمق descender المسافة من baseline إلى الحافة السفلية للرسم الحرفي (descender-line). في الخطوط عالية الجودة، تكون هذه المسافة متوازنة: فdescender قصير جداً يجعل الأحرف ذات descender صعبة التمييز، بينما طويل جداً يخلق مساحة فارغة زائدة بين الأسطر ويقلل كثافة النص. تظهر أنواع الخطوط المختلفة اختلافات كبيرة في طول descender.

نوع الخطDescender / em-sizeأمثلة على أحرف ذات descender
SF Pro~0.22g, j, p, q, y — امتداد متوازن
Roboto~0.24g, j, p — descender معتدل
Playfair Display~0.30g, j, p, q — عناصر زخرفية طويلة
Inter~0.26g, j, p — بشكل ملحوظ أسفل baseline
Noto Sans~0.20g, j — descender قصير، مضغوط

وفقاً لدليل مقاييس خطوط Google (2025)، يعتبر descender مثالياً عندما يكون عمقه 20–25% من حجم em الكامل (1000 FUnits). القيم الأقل من 15% تجعل الأحرف ذات descender صعبة التمييز، بينما القيم الأعلى من 30% تتطلب زيادة إجبارية في line-height لمنع تصادم الأسطر.

المقاييس الرقمية لـ Descender: OpenType وTrueType

في الخطوط الرقمية، يتم تخزين descender كقيمة سالبة في جداول المقاييس. في تنسيق OpenType، هذا هو الحقل hhea.descent (جدول hhea) وsTypoDescender (جدول OS/2). كلا القيمتين سالبتان لأنهما تقاسان من baseline إلى الأسفل. بالنسبة لـ TrueType، يُستخدم جدول OS/2 مع الحقل usWinDescent — قيمته موجبة لكنها تشير إلى نفس المقياس.

python
# قراءة descender من الخط عبر 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)

# تحويل إلى بكسلات لحجم خط 16pt
px_per_em = 16
descent_px = abs(descent_hhea) * px_per_em / 1000  # 8 px

الفرق الحاسم بين المنصات: iOS يستخدم hhea.descent للعرض، بينما Android يستخدم sTypoDescender من OS/2. إذا اختلفت هذه القيم (وهو ما يحدث في الخطوط غير المهيأة جيداً)، فسيتم عرض نفس النص بتباعد أسطر مختلف على iOS وAndroid. فارق 100 FUnits (حوالي 1.6 بكسل بحجم خط 16 نقطة) ملحوظ بالفعل بصرياً.

وفقاً لمواصفات Microsoft OpenType v1.9 (2025)، للعرض الصحيح عبر المنصات، يجب أن تكون قيم hhea.descent وsTypoDescender متساوية بدقة 50 FUnits. عند اختيار خط لتطبيق محمول، يجب التحقق من ذلك عبر fontTools أو أداة مشابهة.

Descender في iOS: UIFont وCore Graphics

في iOS، قيمة descender متاحة عبر الخاصية UIFont.descender. ترجع هذه الخاصية رقماً سالباً يشير إلى المسافة من baseline إلى الحافة السفلية للخط (بما في ذلك descender). على سبيل المثال، لـ SF Pro بحجم 17 نقطة، قيمة descender حوالي -4.2 نقطة. كلما زادت القيمة المطلقة، زاد طول العناصر السفلية الممتدة للخط.

swift
// الحصول على descender في iOS عبر UIFont
let font = UIFont.systemFont(ofSize: 17)
let descender = font.descender     // ~ -4.2 pt لـ SF Pro 17pt
let ascender = font.ascender        // ~ 16.2 pt
let lineHeight = font.lineHeight    // ~ 20.4 pt

// عرض مخصص مع إزاحة descender
let attrString = NSAttributedString(
    string: "Sample text with letter p and y",
    attributes: [.font: font]
)

// Core Text: الحصول على المربع المحيط مع descender
let ctFont = CTFontCreateWithName(
    "SF Pro Text" as CFString, 17, nil
)
let descent = CTFontGetDescent(ctFont)  // ~4.2 pt

عند استخدام TextKit (NSTextStorage, NSLayoutManager)، يتم أخذ descender في الاعتبار تلقائياً في lineFragmentPadding وlineFragmentRect. ومع ذلك، عند القيام بالعرض المخصص عبر Core Graphics (draw(in:))، يجب عليك ضبط الإحداثيات يدوياً بإضافة القيمة المطلقة لـ descender إلى الهامش السفلي للحاوية. إذا لم يتم ذلك، فستتجاوز الأحرف ذات descender حدود العرض وسيتم قصها.

Descender في Android: Paint وCompose

في Android، مقاييس descender متاحة عبر Paint.FontMetrics.descent. على عكس iOS، قيمة descent موجبة — تمثل المسافة من baseline إلى الحافة السفلية للنص. الخاصية FontMetrics.bottom تشمل ليس فقط descender ولكن أيضاً المساحة الإضافية الموصى بها من قبل مصمم الخط (leading). للحساب الدقيق لـ descender فقط، استخدم descent بدلاً من bottom.

kotlin
// الحصول على descender في Android عبر Paint
val paint = Paint().apply {
    textSize = 17 * density
}

val metrics = paint.fontMetrics
val descent = metrics.descent     // ~4.5 px لـ 17sp
val bottom = metrics.bottom       // ~5.0 px مع leading

// عرض مخصص مع إزاحة descender
val baseline = y
canvas.drawText("مثال: gpq", x, baseline, paint)

// الحد السفلي مع descender
val bottomBound = baseline + descent  // الحد السفلي الصحيح

في Jetpack Compose، يمكن الحصول على descender عبر TextLayoutResult. ترجع طريقة getLineBottom الإحداثي Y للحافة السفلية للسطر، والذي يتضمن بالفعل descender. عند القيام بتخطيط مخصص لسلاسل بأحجام خطوط مختلفة (على سبيل المثال، سعر مخفض وسعر كامل)، فإن المحاذاة على baseline مع مراعاة descender تعطي نتيجة أكثر دقة من المحاذاة على الحافة السفلية.

kotlin
// Compose: التحقق من الحد السفلي للنص
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)
            // التحقق من عدم تجاوز descender لحدود الحاوية
        }
    }
)

وفقاً لتنفيذ الطباعة من Google Material Design (2025)، لمنع قص descender في الحاويات ذات الارتفاع الثابت، يجب إضافة حشوة رأسية تساوي على الأقل descent للخط، بغض النظر عن وجود أحرف ذات descender في النص الحالي. هذا يضمن أن الواجهة لن تنكسر عند استبدال النص ديناميكياً.

Descender وتصادم الأسطر في واجهات المحمول

تصادم الأسطر هو حالة يتقاطع فيها descender حرف من السطر العلوي جسدياً مع ascender حرف من السطر السفلي. في واجهات المحمول، يكون هذا ملحوظاً بشكل خاص في العناوين متعددة الأسطر وبطاقات المنتجات وكتل النص ذات التباعد الصغير بين الأسطر. تتفاقم المشكلة عند استخدام خطوط ذات descenders طويلة وline-height صغير.

يمكن حساب الحد الأدنى لـ line-height الذي يمنع التصادم باستخدام الصيغة: line-height = ascender + descender + 2 بكسل هامش. لـ SF Pro بحجم 17 نقطة، هذا يعطي line-height حوالي 16.2 + 4.2 + 2 = 22.4 نقطة (معامل ~1.32). لـ Roboto بحجم 16 sp، حوالي 1.35. إذا كان line-height أقل من هذه القيمة، فالتصادم مضمون في النصوص التي تحتوي على أحرف ذات descender.

swift
// iOS: حساب الحد الأدنى لـ line-height لمنع التصادمات
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
    ]
)

يجب توخي الحذر بشكل خاص عند العمل مع الخطوط الزخرفية والخطوط اليدوية — يمكن أن يصل descender فيها إلى 35–40% من حجم em. نادراً ما تستخدم هذه الخطوط للنص الأساسي ولكن يمكن تطبيقها في العناوين. حتى ظهور واحد لحرف ذو descender طويل في عنوان يمكن أن يسبب تصادماً مع عنصر واجهة مجاور.

الأخطاء الشائعة عند العمل مع Descender

الخطأ الأكثر شيوعاً هو قص descender في الأزرار وحقول النص. عندما نضبط ارتفاع زر أو حقل نص مساوياً لـ line-height دون مراعاة descender، يتم قص الأحرف ذات descender عند الحافة السفلية. هذا ملحوظ بشكل خاص في أزرار النظام ذات الزوايا المستديرة، حيث يمكن أن يتجاوز descender حد نصف قطر الزاوية.

  • الأزرار ذات الارتفاع الثابت — إذا كان ارتفاع الزر يساوي ceil(line-height)، يتم قص الأحرف ذات descender. الحل: زيادة ارتفاع الزر بالقيمة المطلقة لـ descender (4–5 نقطة لخط نظام 17 نقطة) للحشوة العلوية والسفلية.
  • حقل نص دون مراعاة descender — UITextField وEditText القياسيان لديهما حشوة تراعي descender، لكن التطبيقات المخصصة غالباً ما تنساه. تحقق من أن المؤشر وكتلة النص لا يقصان الأحرف ذات descender.
  • خلط أحجام الخطوط في سطر واحد — إذا كان NSAttributedString أو SpannableString يحتوي على أجزاء بأحجام خطوط مختلفة، فقد يتداخل descender للخط الأكبر مع ascender للخط الأصغر. استخدم baselineOffset للتعويض وتحقق من النتيجة.
  • عرض النص في SVG — عند عرض النص في SVG أو على Canvas (خاصة في web views)، قد لا يتم مراعاة descender تلقائياً. حدد دائماً viewBox صريحاً بهامش 10–15% من حجم الخط.

وفقاً لأبحاث الطباعة المحمولة من Nielsen Norman Group (2025)، 41% من التطبيقات المحمولة لديها شاشة واحدة على الأقل حيث يتجاوز النص ذو descenders حدود المكون. يؤدي هذا إلى انخفاض بنسبة 15% في قابلية القراءة وزيادة في وقت إنجاز المهمة من قبل المستخدم. يساعد الاختبار المنتظم بنص يحتوي على أحرف ذات descender في تحديد هذه المشكلات في المراحل المبكرة من التطوير.

الأسئلة الشائعة

كيف يختلف Descender عن baseline؟

Baseline هو الخط الأفقي الذي تستقر عليه الأحرف، بينما descender هو جزء الحرف الموجود أسفل هذا الخط. baseline ثابت للسطر، descender هو خاصية لحرف معين. لا تخلط بين هذه المفاهيم: baseline يُستخدم للمحاذاة، بينما descender يؤثر على تباعد الأسطر ويتطلب مراعاة عند ضبط ارتفاع الحاوية.

كيفية الحصول على descender لخط في Android؟

استخدم Paint.getFontMetrics().descent لنظام View أو TextLayoutResult في Jetpack Compose. على عكس iOS، قيمة descent في Android موجبة وتشير إلى المسافة من baseline إلى الحافة السفلية للرسم الحرفي. لحساب الحد السفلي الكامل للسطر، أضف descent إلى الإحداثي Y لـ baseline.

لماذا يختلف descender لنفس الخط في iOS وAndroid؟

تستخدم المنصات جداول مقاييس مختلفة من ملف الخط: iOS يستخدم hhea.descent، Android يستخدم os/2.sTypoDescender. إذا اختلفت هذه القيم في الخط، سيختلف العرض. تحقق دائماً من كلا القيمتين عبر fontTools. خطوط النظام عالية الجودة (SF Pro, Roboto, Noto) لها مقاييس متسقة لكلتا المنصتين.

ما هو الحد الأدنى لـ line-height اللازم لمنع تصادم descender؟

الحد الأدنى line-height = ascender + descender + 2 بكسل هامش. لخط نظام 17 نقطة على iOS هذا حوالي 22.4 نقطة. على Android لـ Roboto 16 sp — حوالي 22 sp. يُوصى بالتقريب إلى أقرب عدد صحيح والاختبار بسلسلة اختبارية من الأحرف ذات descender — إذا لم يكن هناك تصادم، فإن line-height كافٍ.

هل يمكن استخدام خط ذو descender طويل جداً في تطبيق محمول؟

نعم، ولكن مع تحفظات. الخطوط ذات descender الطويل (Playfair Display، الخطوط الزخرفية) مقبولة للعناوين والنص المميز حيث يمكن زيادة line-height دون المساس بالتصميم. للنص الأساسي، يُفضل استخدام خطوط ذات descender بنسبة 20–25% من حجم em (SF Pro, Roboto, Inter) لتجنب إهدار المساحة الرأسية.

الخلاصة

  • Descender — العنصر السفلي الممتد للحرف الموجود أسفل baseline، موجود في الأحرف اللاتينية «g» و«j» و«p» و«q» و«y».
  • المقاييس الرقمية — hhea.descent (iOS) وos/2.sTypoDescender (Android) في تنسيقات OpenType/TrueType.
  • API iOS — UIFont.descender (قيمة سالبة) لـ UIKit وCTFontGetDescent لـ Core Text.
  • API Android — Paint.FontMetrics.descent (موجب) وTextLayoutResult في Compose.
  • تصادم الأسطر — يحدث عندما line-height < ascender + descender + 2 بكسل هامش؛ يُتحقق بسلسلة اختبارية من الأحرف ذات descender.
  • القص في المكونات — الأزرار وحقول النص والحاويات المخصصة يجب أن تحتوي على حشوة تساوي القيمة المطلقة لـ descender.
  • اختلافات المنصات — iOS وAndroid يستخدمان جداول مقاييس مختلفة، مما يتطلب التحقق من الخط على كلتا المنصتين.

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا