انتقل إلى المحتوى الرئيسي

الفصل 8 · الجزء II · الحرفة

الإعدادات: الموارد والجداول

أنواع الموارد وترقيمها، وأنماط الجداول والتعليقات، والمخططات بحبر واحد، والفيديو المطبوع

آخر تحديث 2026-10-105 دقائقenescaptzhjaar

باختصار

تتناول هذه الصفحة إعدادات الأشكال والجداول والمخططات ومقاطع الفيديو. يسميها Postext موارد ويرقّم كل نوع منها على حدة. تحدد مظهر الجداول: خطوطها وتعبئتها وحروفها وطريقة تقسيمها بين الصفحات. وتحدد طريقة كتابة التعليق المرافق للشكل أو الجدول. ويمكنك أيضًا طباعة المخططات بحبر واحد واختيار طريقة ظهور الفيديو على الورق.

#أنواع الموارد

نوع المورد فئة يحدّدها المستخدم — شكل، جدول، مخطط، شيفرة… — تتحكم في طريقة ترقيم موارد هذا النوع ووضع تعليقاتها والإحالة إليها. تقع القائمة في config.resourceTypes؛ ويحرّرها Sandbox في التصميم › الأشكال والجداول › الترقيم والموضع.

حين لا يُضبط config.resourceTypes، يأتي Postext بثلاثة أنواع افتراضية مدمجة: شكل وجدول وفيديو، يُرقَّم كل منها في تسلسله الخاص {h1}.{n} (ويُعاد العدّ عند كل عنوان من المستوى 1) بعدّادات عشرية. والقائمة التي ليس فيها نوع video، ككتاب حُفظ قبل وجود مقاطع الفيديو، تظل ترقّم موارد الفيديو ذات النوع video: يُضاف إليها نوع الفيديو المدمج من أجلها (effectiveResourceTypes(config, resources))، وتعيده defaultVideoResourceType(locale) وحده. وتُسمّى الأنواع بلغة المستند: config.locale، وإلا bodyText.hyphenation.locale، وإلا الإنجليزية (انظر لغة المستند).

الأنواع الافتراضية المدمجة تراعي اللغة. فالدالة المصدَّرة defaultResourceTypes(locale = 'en') تترجم أسماء الأنواع والتسميات المختصرة وبادئات التعليقات إلى لغة المستند — تعطي الإنجليزية Figure/Fig. وTable/Tab.؛ وتعطي الإسبانية Figura/Fig. وTabla/Tabla؛ وللفرنسية والألمانية والإيطالية والبرتغالية والكتالونية والهولندية تسمياتها أيضًا (يسردها الجدول في لغة المستند). وتُحلّ الوسوم الإقليمية مثل es-ES بحسب اللغة، وأي لغة بلا ترجمات تعود إلى الإنجليزية. أما سلوك الترقيم (numberingTemplate: '{h1}.{n}'، resetOn: 'h1'، العدّادات العشرية) فمستقل عن اللغة. وكل استدعاء يعيد كائنات جديدة، فيمكنك تعديل النتيجة بحرية:

import { defaultResourceTypes } from 'postext';
 
const types = defaultResourceTypes('es');
// => [{ id: 'figure', name: 'Figura', shortLabel: 'Fig.', captionPrefix: 'Figura',
//       numberingTemplate: '{h1}.{n}', resetOn: 'h1', counterFormat: 'decimal', … },
//     { id: 'table',  name: 'Tabla',  shortLabel: 'Tabla', captionPrefix: 'Tabla', … },
//     { id: 'video',  name: 'Vídeo',  shortLabel: 'Vídeo', captionPrefix: 'Vídeo', … }]
type ResourceCounterFormat =
  | 'decimal'
  | 'roman-lower'
  | 'roman-upper'
  | 'alpha-lower'
  | 'alpha-upper';
 
type ResourceCounterReset = 'never' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
 
interface ResourcePlacement {
  position?: 'auto' | 'top' | 'bottom' | 'here'; // which free slot a float may take; 'here' = inline embed at the ::resource directive
  span?: 'column' | 'page' | 'side';             // one column, the full content width, or the float-only side column
  rotate?: 'ccw' | 'cw';                         // a quarter turn: a landscape table on a page of its own
  width?: number;                                // fraction (0 < width < 1) of the column or page width; default: the whole width
  align?: 'left' | 'center' | 'right';           // where a float narrower than its column sits; default 'left'
  captionSide?: boolean;                         // caption beside the figure, in the side column of a oneAndHalf layout (column floats only)
  columns?: number;                              // عدد الأعمدة المتجاورة التي يمتد عليها عنصر 'column' العائم (منذ 1.18)
  wrap?: 'none' | 'left' | 'right' | 'start' | 'end'; // نص بجانب المورد، في ذلك الجانب من عموده (منذ 1.24)
  wrapGap?: Dimension;                           // المسافة بين المورد الملتف والنص (منذ 1.24)
}
 
interface ResourceType {
  id: string;                          // stable id, referenced by Resource.typeId
  name: string;                        // singular display name, e.g. "Figure"
  namePlural?: string;                 // optional plural, e.g. "Figures"
  shortLabel: string;                  // compact label for inline refs, e.g. "Fig."
  numberingTemplate: string;           // "{h1}.{n}" or "{n}"
  resetOn: ResourceCounterReset;       // when the {n} counter resets
  counterFormat: ResourceCounterFormat;// how {n} is formatted
  captionPrefix: string;               // prepended to the caption, e.g. "Figure"
  defaultPlacement?: ResourcePlacement;// fallback placement for this type's resources
}

ResourcePlacement هو الشكل نفسه الذي يضبطه المورد في placement الخاص به. يختار position نوع الموضع الشاغر الذي يجوز للعنصر العائم أن يشغله — auto (الافتراضي) يأخذ أول موضع بعد الإحالة الأولى، وtop / bottom يقصرانه على هذا النوع من الأشرطة، وhere يُدرج المورد في مكانه ضمن النص. ويحدّد span امتداد العنصر العائم: عمود واحد، أو عرض المحتوى كاملًا، أو العمود الجانبي المخصص للعناصر العائمة في تخطيط العمود ونصف العمود. ويدير rotate المورد ربع دورة ويجعله عنصرًا عائمًا بعرض الصفحة في صفحة مستقلة. ويضيّق width العنصر العائم إلى كسر من عموده (أو من الصفحة للعنصر العائم بعرض الصفحة) — جدول صغير في عمود عريض مثلًا. ويحدّد align موضع هذا العنصر العائم الأضيق — إلى اليسار افتراضيًا، أو في الوسط، أو إلى اليمين — وموضع الصورة الأضيق من موضعها داخله: صورة نقطية أصغر من العمود، أو صورة صغّرها layout.fitFiguresToPage. ويحتفظ التعليق والملاحظة بعرض الموضع. (حتى postext 1.4 كانت هذه الصورة تُنضَّد دائمًا ملاصقة لليسار.) ويضع captionSide التعليق بجانب الشكل في العمود الجانبي المخصص للعناصر العائمة في تخطيط العمود ونصف العمود (layout.sideColumnRole: 'floats')، بمحاذاة أعلى الشكل (أو أسفله للعنصر العائم في أسفل الصفحة)؛ ولا ينطبق إلا على العناصر العائمة بعرض العمود، والصفحة التي ليس فيها عمود كهذا تُبقي التعليق تحت الشكل. وحين لا يضبط المورد ولا نوعه موضعًا، يكون الافتراضي المدمج auto / column. ويصغّر shrink ('never'، 'page'، 'slot') وminScale (0.7 إن لم يُضبط) الصورةَ العائمة إلى حيّز موضعها بدل نقلها إلى ما بعده، ويصفّ captionMeasure: 'body' تعليق الصورة الأضيق من موضعها وملاحظتها بعرض الصورة (انظر تنسيق المستند › الموضع)؛ ويعطي layout.floatShrink القيمة الافتراضية للمستند للمفتاحين الأولين. يضع wrap الإدراج داخل النص أو العنصر العائم بعرض عمود واحد في أحد جانبي عموده والنص يُصفّ بجانبه، على بُعد wrapGap؛ ويحفظ layout.wrap القيم الافتراضية (انظر تنسيق المستند › التفاف النص). ويجعل citingPage الشكلَ العائم top أو auto في رأس الصفحة أو العمود حيث يقع سطر الإحالة بدلًا من أول موضع شاغر بعده؛ ويعطي layout.floatsAtCitingPage القيمة الافتراضية للمستند وlayout.maxTopFraction النسبة التي يجوز أن يأخذها من العمود (انظر تنسيق المستند › الموضع).

يضع columns (منذ postext 1.18) عنصرًا عائمًا span: 'column' على هذا العدد من الأعمدة المتجاورة في صفحة ذات أعمدة عدة: صورة تمتد على عمودين من أعمدة الجريدة الخمسة. وعرضه هو عرض تلك الأعمدة والفواصل التي بينها. ويأخذ رأس سلسلة من الأعمدة الفارغة التي تبدأ على مستوى واحد، أو أسفل العمود الذي يشير إليه والأعمدة الفارغة التي تليه؛ وإذا بلغ عدد الأعمدة عدد أعمدة الصفحة أو زاد عليه صار عنصرًا عائمًا بعرض الصفحة. ويُتجاهل مع span بالقيمتين 'page' و'side'، ومع المورد المدوَّر (rotate)، ومع الإدراج المضمّن (here)، ولا يسري captionSide إلا على عنصر عائم بعرض عمود واحد.

الخاصيةالنوعالوصف
idstringمعرّف ثابت يشير إليه typeId في كل مورد. يُضبط مرة واحدة عند إنشاء النوع؛ وحذف نوع لا تزال موارد تشير إليه يثير تحذير نوع معلّق.
namestringاسم العرض بالمفرد. تستخدمه الإحالة السطرية style="full" (مثلًا Figure 1.7).
namePluralstring (اختياري)اسم العرض بالجمع، لتسميات الواجهة وقوائم الموارد.
shortLabelstringاختصار موجز تستخدمه طريقة الإحالة السطرية الافتراضية (مثلًا Fig. 1.7).
numberingTemplatestringقالب الرقم المحسوب. انظر رموز القالب أدناه. والأشكال الشائعة (ضمن الفصل، مثلًا 2.3) و (عدّ متصل واحد).
resetOnResourceCounterResetتعطي 'never' عدًّا متصلًا واحدًا على امتداد المستند؛ وتعيد 'h1'..'h6' ضبط العدّاد كلما صودف عنوان من ذلك المستوى (أو من أي مستوى أعلى منه). اضبطها لتطابق مستوى العنوان الوارد في القالب — مثلًا مع resetOn: 'h1'.
counterFormatResourceCounterFormatطريقة عرض العدّاد : عشري (1, 2, 3)، أو روماني صغير/كبير (i, ii / I, II)، أو أبجدي صغير/كبير (a, b / A, B). وتُقبل أيضًا الكتابات المستخدمة للصفحات والقوائم ('lower-roman'، 'arabic'…؛ انظر كتابات صيغ الترقيم)؛ والقيمة غير المعروفة تعدّ بالعشري ويُبلغ عنها. أما رموز العناوين (…) فتُعرض دائمًا أعدادًا عشرية.
captionPrefixstringالنص الذي يُضاف قبل تعليق الشكل/الجدول. يلي الرقمُ المحسوب البادئةَ — فيُعرض التعليق على هيئة . ، مثلًا الشكل 1.7. المخطط الأصلي. والنوع الذي يكون numberingTemplate فيه فارغًا لا رقم له، ويُقرأ تعليقه . . وتُحذف المسافات في نهاية البادئة، والبادئة التي تنتهي أصلًا بـ . أو : أو ! أو ? أو … (أو بصيغتها كاملة العرض) لا تأخذ نقطة ثانية: Pl. Lines at 0°.
defaultPlacementResourcePlacement (اختياري)الموضع الذي تستخدمه موارد هذا النوع التي لا تضبط placement الخاص بها: position وspan وrotate وwidth وalign وcaptionSide وcolumns، ويُحلّ كل منها مستقلًا. وحين لا يضبط المورد ولا النوع حقلًا ما، ينطبق الافتراضي المدمج: auto / column، قائمًا، بالعرض الكامل، محاذيًا لليسار، والتعليق تحت الشكل. انظر الترقيم والإحالات أدناه لسلسلة الحل، وصيغة المستند › الموارد لما تفعله كل قيمة، بما فيها الموارد المُدارة.
captionStyleCaptionStyleConfig (اختياري)تجاوز جزئي لـنمط التعليق لموارد هذا النوع. لا تحلّ محل captionStyle العام إلا المفاتيح التي تضبطها؛ ويُورث كل ما عداها (وcolor المتجاوَز يحدّد أيضًا لونَي التسمية والملاحظة ما لم يُضبطا صراحة). الاستخدام المعتاد: جداول تعليقها فوقها على شريط ملوّن بينما تُبقي الأشكال تعليقها تحتها. وتُحلّ مراجع لوحة الألوان كأي لون آخر.

#رموز القالب

يُعرض numberingTemplate بالمحرّك نفسه الذي يرقّم العناوين (انظر العناوين). ويتعرّف على نوعين من الرموز:

  • {n} — عدّاد النوع، منسَّقًا وفق counterFormat. وهي القيمة التي تزيد مع كل مورد ويُعاد ضبطها وفق resetOn.
  • {h1} … {h6} — أرقام العناوين السارية عند موضع الإحالة الأولى، وتُعرض دائمًا أعدادًا عشرية. {h1} رقم العنوان الحالي من المستوى 1، و{h2} من المستوى 2، وهكذا.

وأي نص آخر يُطبع حرفيًا. والشرطة المائلة العكسية تجعل { أو } أو \ حرفًا عاديًا. وحين لا تكون لرمز عنوان قيمة في النطاق (مثل {h1} قبل أي عنوان من المستوى 1)، يُحذف مع الفاصل المجاور له — فيؤول {h1}.{n} إلى العدّاد وحده دون خلل.

القالب الفارغ ('') لا يطبع رقمًا، وإن ظل النوع يعدّ موارده: يُقرأ التعليق Do. Lines at 0° وتطبع :ref التسمية وحدها (Do). وحتى postext 1.4 كان هذا التعليق يُقرأ Do .Lines at 0° وكانت الإحالة تنتهي بمسافة غير قابلة للكسر.

القالبمع h1 = 2، والعدّاد = 3ملاحظات
3عدّ متصل واحد. استخدمه مع resetOn: 'never'.
2.3ضمن الفصل. استخدمه مع resetOn: 'h1'.
2.0.3ضمن القسم. استخدمه مع resetOn: 'h2'.

#الترقيم والإحالات

الرقم الذي ينتجه نوع المورد هو ما تطبعه :ref وما تسبقه بادئة التعليق. والصيغة :ref{id} هي الصيغة الأساسية: الإحالة الأولى بترتيب القراءة تُدرِج المورد، فيطفو إلى أول موضع شاغر بعد تلك الإحالة — أسفل العمود المُحيل، أو أعلى العمود الفارغ التالي أو أسفله، أو شريط في الصفحة التالية (وفق موضعه المحلول — position: 'auto' | 'top' | 'bottom' | 'here' وspan: 'column' | 'page' | 'side'، إضافة إلى rotate وwidth وalign وcaptionSide، تُحلّ لكل مورد، ثم من defaultPlacement الخاص بالنوع، ثم من الافتراضي المدمج auto / column؛ و'top' / 'bottom' يقصران البحث على هذا النوع من المواضع). أما التضمين الكتلي ::resource{id} فاختياري، ولا يلزم إلا مع placement.position: 'here' — تضمين سطري غير عائم عند نقطة محددة في التدفّق. والقواعد الكاملة من جهة المستند — الصيغتان وخيارا style وtext في :ref — موثّقة في صيغة المستند › الموارد، بما في ذلك كيف يحدّد ترتيب الإحالات الأولى العدّ.

وهذا يماثل ترقيم العناوين: فكما يحمل مستوى العنوان numberingTemplate، يحمل نوع المورد واحدًا أيضًا — لكن عدّاد المورد ({n}) يتقدّم مع كل إحالة أولى لا مع كل عنوان، وresetOn يربطه من جديد بتسلسل العناوين.

#ما الذي يُرقَّم

يُرقَّم المورد حين يحيل إليه النص — بـ :ref أو بتضمين ::resource — بترتيب تلك الإحالات الأولى، أيًّا كان موضعه: عائمًا، أو سطريًا (here)، أو في العمود الجانبي، أو مُدارًا. والمورد الذي لا يرسمه إلا تصميم — عنصر image في صفحة افتتاح الفصل، أو في ترويسة، أو في صفحة جزء — أو الذي لا يحيل إليه شيء، لا يأخذ رقمًا ولا يُقدّم عدّاد نوعه. ففي مقال مصوّر لوحاته الممتدة حتى حافة النزف صورٌ لصفحات الافتتاح ولوحته الأصغر الوحيدة عنصر عائم مُحال إليه، تكون تلك اللوحة العائمة هي اللوحة I، مهما عرضت صفحات الافتتاح من لوحات قبلها؛ رقّم لوحات الافتتاح في تصميمها (بسمة مثل {attr.plate}) واحتفظ بالعدّاد للوحات التي يستشهد بها النص.

في كتاب يُخرَج فصلًا فصلًا (Sandbox، أو buildBundle، أو buildDocument مع العدّادات التي تمرّرها continuationAfter())، تكون الإحالة الأولى في الكتاب كله هي التي تُحتسب: يحتفظ المورد بالرقم الذي أخذه في الفصل الذي يحيل إليه أولًا، ولا يضعه إلا ذلك الفصل. وتطبع :ref في فصل لاحق ذلك الرقم ولا تضع شيئًا، وتضمين ::resource لمورد عائم هناك ليس إلا إحالة أخرى (أما التضمين السطري here فيُنضَّد حيث كُتب). وترتبط هذه الإحالة بالشكل حين يكون الشكل في المُخرَج نفسه: فملف PDF للكتاب كله يربطها بصفحة الفصل السابق. أما الفصل المعروض وحده، في HTML أو PDF، فينضّدها نصًا عاديًا بلون الروابط، لأن شكلها ليس في ذلك المستند. والمضيف الذي يضم HTML الفصول في صفحة واحدة يمرّر إلى renderToHtml الموارد التي ترسيها الفصول، في refTargets، فترتبط هذه الإحالة بشكل الفصل السابق من جديد:

import { anchoredResourceIds, buildBundle, renderToHtml } from 'postext';
 
const docs = buildBundle(bundle);
const refTargets = new Set(docs.flatMap((d) => [...anchoredResourceIds(d)]));
const html = docs.map((d) => renderToHtml(d, { refTargets })).join('');

تغيّر في postext 1.5: حتى 1.4 كان كل فصل يحيل إلى شكل يعوّمه من جديد، وكان HTML كل :ref رابطًا، سواء أكان شكله في الصفحة أم لا.

{h1} هو العدّ المتصل للعناوين من المستوى 1: يتقدّم مع كل H1 ما لم يضبط نمط عنوانه numbered: false — فـ numberingTemplate الفارغ يُخفي رقم العنوان ولا يوقف العدّ. لذلك يرقّم المقال الذي ليس فيه H1 سوى عنوانه أشكاله 1.1، 1.2… مع النوعين المدمجين {h1}.{n}. وهناك طريقتان لطباعة الشكل 1، 2…:

  • نوع مرقّم {n} مع resetOn: 'never' (مع resetOn: 'h1' يبدأ العدّ من جديد عند كل H1)؛
  • نمط عنوان مع numbered: false على العنوان الرئيسي، وعلى أي H1 آخر لا ينبغي أن يُحتسب: هذا العنوان لا يقدّم {h1} بل يتركه كما كان — فارغًا قبل أول H1 محتسب، حيث يؤول {h1}.{n} إلى العدّاد وحده — ولا يطلق resetOn: 'h1' أبدًا، فيستمر العدّ عبره. بعد # Introduction والشكل 1.1 فيه، يكون أول شكل تحت # Appendix غير المرقّم هو 1.2، لا 2.1.
// Figure 1, 2, 3… in a single-article document
resourceTypes: defaultResourceTypes('en').map((t) => ({ ...t, numberingTemplate: '{n}', resetOn: 'never' })),

#نمط الجداول

تتحكم الخاصية tableStyle في خطوط موارد الجداول وزخرفتها، أي في كل جدول ما لم يختر نمط جدول مسمّى. تُنسَّق خلايا المتن وخلايا الرأس كلٌّ على حدة. وحين لا تُحدَّد عائلة الخط وحجمه وألوانه، تُورَث من نص المتن بعد حسمه، فالمستند الذي ليس فيه tableStyle يرسم جداوله بخطوط نص المتن.

const config: PostextConfig = {
  tableStyle: {
    headerBold: true,
    headerBackground: { hex: '#f0f0f0', model: 'hex' },
    borders: true,
    borderWidth: { value: 0.75, unit: 'pt' },
  },
};
الخاصيةالنوعالقيمة الافتراضيةالوصف
bodyFontFamilystringخط نص المتنعائلة الخط لخلايا المتن.
bodyFontSizeDimensionحجم نص المتنحجم الخط لخلايا المتن.
bodyColorColorValueلون نص المتنلون النص في خلايا المتن.
headerFontFamilystringخط نص المتنعائلة الخط لخلايا الرأس.
headerFontSizeDimensionحجم نص المتنحجم الخط لخلايا الرأس.
headerColorColorValueلون نص المتنلون النص في خلايا الرأس.
headerBoldbooleantrueارسم خلايا الرأس بخط غامق.
headerItalicbooleanfalseارسم خلايا الرأس بخط مائل.
headerLetterSpacingDimension0ptالتتبّع (tracking) بعد كل حرف من خلية رأس، بما في ذلك المسافات، كما في الخاصية letter-spacing في CSS. القيم الموجبة تباعد بين الحروف (رأس بالحروف الكبيرة يحتاج عادةً من 0.05em إلى 0.1em)، والسالبة تقاربها. وحدة em هي حجم خط الرأس. تُقاس أسطر الرأس مع التتبّع، فتلتف وتتوسّط وتُحاذى وهو محسوب فيها، وترسمه Canvas وHTML وPDF على النحو نفسه. ويسري على كل خلية رأس: صفوف الرأس وأي خلية معلَّمة بـ isHeader.
headerTextTransform'none' | 'uppercase''none'نضّد خلايا الرأس بالحروف الكبيرة. يحتفظ النص بطوله، فيبقى Sandbox قادرًا على ربط كل حرف بموضعه في المصدر: الحرف الذي صيغته الكبيرة أطول (ß) يبقى كما هو. وتحتفظ الإحالات إلى الموارد بتسميتها.
headerBackgroundEnabledbooleantrueارسم تعبئة خلف صف الرأس.
headerBackgroundColorValue#f0f0f0لون تعبئة صف الرأس.
bodyBackgroundEnabledbooleanfalseارسم تعبئة خلف صفوف المتن.
bodyBackgroundColorValue#ffffffلون تعبئة صفوف المتن (لا يُرسم إلا عند التفعيل).
bodyAlternateBackgroundEnabledbooleanfalseصفوف متناوبة الألوان: املأ كل صف ثانٍ من صفوف المتن بـ bodyAlternateBackground. انظر الصفوف المتناوبة الألوان.
bodyAlternateBackgroundColorValue#f2f2f2تعبئة صفوف المتن المتناوبة (لا تُرسم إلا عند التفعيل).
bordersbooleantrueارسم حدود الخلايا.
borderColorColorValueلون نص المتنلون خط الحدود.
borderWidthDimension0.75ptسُمك خط الحدود (≈1px عند 96 DPI؛ يتغيّر مع دقة الصفحة DPI). لا يستعمله 'booktabs'، فله سُمكه الخاص.
cellPaddingDimension0.375emالحشوة الداخلية لكل خلية.
rules'grid' | 'horizontal' | 'outer' | 'none' | 'booktabs''grid'أي الخطوط تُرسم حين تكون borders مفعّلة: شبكة الخلايا كاملة، أو الخطوط الأفقية وحدها (الحافة العليا والسفلى لكل صف، بلا خطوط عمودية)، أو الإطار الخارجي وحده، أو لا شيء، أو خطوط جدول المجلات الثلاثة (انظر خطوط booktabs).
borderRadiusDimension0نصف قطر زوايا الإطار الخارجي للجدول. يُرسم الإطار مستدير الزوايا (مع الخطوط grid أو outer)، وتُقَصّ عليه تعبئات الخلايا وخلفية الرأس، حتى مع rules: 'none' أو مع تعطيل الحدود، وتُشذَّب الخطوط الأفقية عند محيطه الخارجي؛ أما الخطوط الداخلية فتبقى مستقيمة. والجدول المقسوم على عدة صفحات يستدير الزاويتان العلويتان من جزئه الأول والسفليتان من جزئه الأخير. لا تتجاوز القيمة نصف عرض الجدول ونصف ارتفاعه. تبقى خطوط 'booktabs' مستقيمة (أما التعبئة فتُقصّ).
heavyRuleWidthDimension0.08embooktabs: الخطان فوق الجدول وتحت صفه الأخير. الـem هو حجم خط خلايا المتن.
lightRuleWidthDimension0.05embooktabs: الخط تحت صفوف الترويسة، وخطوط المجموعات.
spanRuleWidthDimension0.03embooktabs: الخطوط تحت خلايا الترويسة الممتدة على أكثر من عمود.
spanRules'trimmed' | 'full' | 'none''trimmed'booktabs: الخطوط تحت خلايا الترويسة الممتدة على أكثر من عمود، فوق آخر صف من الترويسة: مقصّرة من طرفيها بمقدار spanRuleTrim، أو بعرض الخلية كله، أو لا شيء.
spanRuleTrimDimension0.5embooktabs: مقدار ما يُقصَّر من كل طرف من الخط المقصّر.
groupRulesbooleanfalsebooktabs: خط رفيع فوق كل صف من المتن يرأس مجموعة.
continuedFootRule'bottom' | 'light' | 'none''light'booktabs: ما يختم جزء الجدول المقسوم الذي يتبع في الصفحة التالية.
overflow'split' | 'clip' | 'hide''split'مصير الجدول الأطول من الصفحة: يستمر في الصفحات التالية، أو تُبقى الصفوف التي تتسع فقط، أو يُترك كله. ومع splitInline، مصير الجدول الموضوع داخل النص الذي لا يتسع له ما تبقى من عموده أيضًا. انظر أدناه.
splitInlinebooleantrueيطبّق overflow على الجداول الموضوعة في الموضع here أيضًا: فالجدول المدرج الذي لا تتسع له المساحة الباقية في عموده يُقطع بين الصفوف ويستمر في رأس العمود التالي. وتنقل false هذا الجدول كاملًا إلى العمود التالي، كما كان حتى postext 1.24؛ والإعدادات التي خزّنتها إصدارات سابقة وتضمّن فصولها موردًا تُقرأ بالقيمة false. انظر الجداول الأطول من الصفحة. منذ postext 1.25.
continuedSuffixstring'(cont.)'يُلحق بخط مائل بتعليق كل جزء تالٍ من جدول مقسوم، بعد مسافة؛ واللاحقة التي تبدأ بحرف صيني أو بحرف كامل العرض ('(续)') تُنضَّد ملاصقة للتعليق بلا مسافة.
continuesMarkerEnabledbooleantrueضع علامة تحت كل جزء يستمر في الصفحة التالية.
continuesMarkerstring'Continued' / 'Continúa'نص تلك العلامة، يُنضَّد محاذيًا إلى اليمين تحت الجزء بخط الملاحظة (انظر نمط التعليقات). تتبع القيمة الافتراضية لغة المستند (انظر لغة المستند للغات الثماني).

يُحتفظ بسُمك الحدود كسريًا: الخط الذي سُمكه 0.5pt يُرسم خطًا شعريًا في PDF وعلى الشاشة بدل أن يُقرَّب إلى بكسل كامل (الحد الأدنى 0.25px).

#الصفوف المتناوبة الألوان

يسهل تتبّع الجداول الطويلة للبيانات أفقيًا حين يُلوَّن كل صف ثانٍ. يفعّل bodyAlternateBackgroundEnabled الأشرطة، ويحدد bodyAlternateBackground لونها:

const config: PostextConfig = {
  tableStyle: {
    bodyBackgroundEnabled: true,
    bodyBackground: { hex: '#ffffff', model: 'hex' },
    bodyAlternateBackgroundEnabled: true,
    bodyAlternateBackground: { hex: '#eef3fa', model: 'hex' },
  },
};

تُعدّ الصفوف بدءًا من أول صف بعد صفوف الرأس (TableModel.headerRowCount، أو الصفوف الأولى المكوّنة من خلايا رأس): يحتفظ ذلك الصف بـ bodyBackground، أو يبقى بلا تعبئة ما دام bodyBackgroundEnabled معطّلًا، ويأخذ الصف التالي التعبئة المتناوبة، وهكذا. يتبع العدُّ نموذجَ الجدول لا الصفحة، فالجدول المقسوم على عدة صفحات يحتفظ بشريط كل صف في كل صفحة، والخلية المدموجة عبر عدة صفوف تأخذ شريط صفها الأول. تحتفظ خلايا الرأس بتعبئة الرأس، وتتقدّم خلفية الخلية الخاصة background على الاثنتين، واللون المرتبط بلوحة الألوان يتبع لوحة الألوان. ونمط الجدول المسمّى يحدد هذين الحقلين كأي حقل آخر، فيمكن أن يكون نمط واحد مخطّطًا وجداول المستند الأخرى غير مخطّطة؛ وهما في Sandbox مفتاح صفوف متناوبة الألوان ولونه، تحت خلايا المتن.

في الـ VDT تحمل خلايا الصفوف المتناوبة alternate: true، ويحمل تخطيط الجدول bodyAlternateBackground. تعيد tableCellFill(table, cell) التعبئة التي تُرسم بها الخلية، أي تعبئتها الخاصة أو تعبئة الرأس أو التعبئة المتناوبة أو تعبئة المتن، وهي ما ترسمه المُخرِجات الثلاثة: Canvas وHTML وPDF. وتلتقي التعبئات المتجاورة بلا فاصل: المتصفح عند نسبة بكسل كسرية أو عارض PDF ينعّم حواف كل تعبئة وحدها فيترك الصفحة تظهر في خط شعري بين خليتين، لذلك يرسم مُخرِجا HTML وPDF tableCellFillRects(table)، أي تعبئة كل خلية مع شريط يمتد عبر كل حافة تشترك فيها مع خلية تُرسم بعدها فتغطيه تلك الخلية، ويُحاذي Canvas تعبئاته على بكسلات الجهاز.

#خطوط booktabs

تُصفّ جداول المجلات والكتب المدرسية عادةً بثلاثة خطوط ولا خطوط عمودية: خط سميك فوق الجدول، وخط رفيع تحت الترويسة، وخط سميك تحت الصف الأخير، مع خطوط قصيرة تحت الترويسات التي تجمع أكثر من عمود (حزمة booktabs في LaTeX: \toprule و\midrule و\cmidrule و\bottomrule). يرسم rules: 'booktabs' هذا النمط:

const config: PostextConfig = {
  tableStyle: {
    rules: 'booktabs',
    borderColor: { hex: '#000000', model: 'hex' },
    headerBackgroundEnabled: false,
  },
};
  • سُمك الخط فوق الجدول والخط تحت صفه الأخير heavyRuleWidth (0.08em)، وسُمك الخط تحت صفوف الترويسة lightRuleWidth (0.05em). الجدول الذي لا صفوف ترويسة فيه لا خط ترويسة له.
  • خلية الترويسة الممتدة على أكثر من عمود فوق آخر صف من الترويسة يُرسم تحتها خط سُمكه spanRuleWidth (0.03em). مع spanRules: 'trimmed' (الافتراضي) يُقصَّر الخط بمقدار spanRuleTrim (0.5em) من طرفيه كي لا يتلامس الخطان تحت ترويستين متجاورتين؛ و'full' يمدّه بعرض الخلية كله، و'none' يحذفه.
  • يضيف groupRules: true خطًّا رفيعًا فوق كل صف من المتن يرأس مجموعة (خلية واحدة تمتد عبر الجدول كله، أو صف من خلايا الترويسة)، إلا إذا افتتح الصفُّ الجدولَ أو صفحةً، فهناك خط الترويسة.
  • يُحسب السُّمك بالنسبة إلى حجم خط خلايا المتن (bodyFontSize)، فلا يزيد حجمُ خط ترويسة أكبر سُمكَ خطها. والسُّمك 0 يحذف ذلك الخط.
  • تأخذ الخطوط اللون borderColor (واللون المرتبط باللوحة يتبع اللوحة و:::part palette)، وborders: false يطفئها. لا يُطبَّق borderWidth ولا borderRadius: تبقى الخطوط مستقيمة، بينما تُقصّ تعبئة الخلايا على الإطار المدوّر. وتعمل تعبئة الترويسة والصفوف المتناوبة الألوان وbackground الخاص بالخلية كما في الأنماط الأخرى، تحت الخطوط.
  • الجدول المقسوم على صفحات يكرر صفوف ترويسته في كل جزء، فيبدأ كل جزء بالخط السميك وخط الترويسة. ولا يختم الخطُّ السميك تحت الصف الأخير إلا الجزءَ الأخير؛ أما الجزء الذي يتبع في الصفحة التالية فينتهي بـ continuedFootRule: خط رفيع ('light'، الافتراضي)، أو الخط السميك ('bottom')، أو لا شيء ('none').

يحسب التخطيط الخطوط مرة واحدة. يحملها جدول الـVDT في strokes ({ x1, y1, x2, y2, widthPx }، نسبةً إلى الزاوية العليا اليسرى من متن الجدول)، وترسمها اللوحة وعارض HTML وملف PDF وEPUB ذو التخطيط الثابت كما هي؛ وفي PDF الموسوم هي عناصر تخطيط. ويكتبها EPUB المرن حدودَ CSS على الجدول وترويسته، والخطوط المقصّرة خطوطَ خلفية. وفي Sandbox يُظهر اختيار booktabs في قائمة الخطوط الفاصلة هذه الحقول ويُخفي سُمك الحدود ونصف قطر الزوايا.

#أنماط الجداول المسمّاة

قلّما يضبط المستند كل جداوله على هيئة واحدة: قائمة تحقق في شبكة كحلية بإطار مستدير، وصف خيارات لا يحيط به إلا إطاره الخارجي، وجدول بيانات بخطوط أفقية بسيطة. تعلن tableStyles متغيّرات مسمّاة، ويختار مورد الجدول واحدًا منها بـ table.styleId. وكل حقل لا يحدده النمط يُقرأ أولًا من tableStyle ثم من نص المتن، فلا يذكر النمط إلا ما يميّز جداوله. والجدول الذي ليس له styleId، أو له معرّف لا يعلنه أي نمط، يبقى على tableStyle، والمستند الذي ليس فيه tableStyles يُرسم تمامًا كما كان من قبل.

const config: PostextConfig = {
  tableStyle: {
    borderColor: { hex: '#163a76', model: 'hex' },
    borderWidth: { value: 1.3, unit: 'pt' },
    borderRadius: { value: 10, unit: 'pt' },
  },
  tableStyles: [
    {
      id: 'option',
      name: 'Option row',
      rules: 'outer',
      borderColor: { hex: '#7a9cc6', model: 'hex' },
      borderWidth: { value: 1, unit: 'pt' },
      borderRadius: { value: 8, unit: 'pt' },
      headerBackgroundEnabled: false,
    },
  ],
};
 
// In the resources: this table is set in the "option" style.
const resource: Resource = {
  id: 'choices', typeId: 'table', kind: 'table', createdAt: 0, updatedAt: 0,
  table: { model: { rows: [/* … */] }, styleId: 'option' },
};

يأخذ كل مدخل كل حقول tableStyle إضافةً إلى id (ما تشير إليه table.styleId) وname اختياري للمحرّر (قيمته الافتراضية هي المعرّف). وكل ما يستطيع النمط تحديده يسري على كل جدول على حدة: الخطوط، والتعبئات، والحدود، والخطوط الفاصلة، ونصف قطر الزوايا، والحشوة، وسلوك الفيض مع نصوص الاستمرار. تعيد resolveTableStylesConfig(styles, tableStyle, resolvedBodyText, locale?) القائمة المحسومة، وتعيد pickTableStyle(resolved, styleId) النمط الذي يُنضَّد به الجدول، وتحذف stripTableStylesDefaults الحقول غير المحددة (وتُبقي الحقل المساوي لقيمته الافتراضية المدمجة، لأنه يتقدّم على قيمة مختلفة في tableStyle). وفي EPUB المرن يكون النمط المسمّى صنفًا (class) على الجدول (pt-table-<id>) تنسّقه ورقة أنماط الكتاب.

#الجداول الأطول من الصفحة

الجدول العائم الذي لا يتسع في الصفحة الجديدة المعروضة عليه لا يُضغط ولا يفيض: مع overflow: 'split' (القيمة الافتراضية) يقطعه المحرّك بين الصفوف عند آخر حافة تتسع في الصفحة ويتابعه في الصفحات التالية، مهما بلغ عددها. يكرر كل جزء تالٍ صفوف رأس الجدول (TableModel.headerRowCount، أو الصفوف الأولى المكوّنة من خلايا رأس حين لا تكون محددة) ويحمل التعليق من جديد مع continuedSuffix بعد الوصف: «Table 6-4. Title (cont.)». وكل جزء يستمر تحته continuesMarker، محاذيًا إلى اليمين، بخط الملاحظة؛ وتُؤجَّل ملاحظة الجدول إلى الجزء الأخير. لا يمر القطع أبدًا عبر خلية مدموجة (الخلية الممتدة على عدة صفوف تنتقل كاملة إلى الجزء التالي)، والصف الذي يتصدّر الصفوف التي تحته، أي خلية واحدة تمتد على عرض الجدول كله، يُنقل إلى الجزء التالي بدل أن يبقى معزولًا في أسفل الصفحة. ويختم جدول booktabs كل جزء يتبع بـ continuedFootRule (انظر خطوط booktabs).

أين ينتهي الجزء الأول. الجدول الذي يُعرض عليه رأس عمود فارغ بعد الإحالة إليه يأخذ الصفوف التي تتسع هناك ويستمر في الموضع التالي. ويملأ العمود حتى أسفله حين ينفرد بالعمود: الجزء الذي سيترك تحته أقل من ثلاثة أسطر من نص المتن يأخذها هي أيضًا، بدل أن يترك بقية قصيرة من النص. وحين يضم العمود شريطًا عائمًا آخر، كشكل بعرض الصفحة في رأسها مثلًا، يتوقف الجزء قبل أسفل العمود بثلاثة أسطر من نص المتن على الأقل، وهي المساحة التي يتركها أي عنصر عائم للنص حين يشارك عنصرًا آخر عمودًا واحدًا، فينتهي العمود بشيء من النص تحت الجدول لا بعناصر عائمة وحدها. ولكي يمتد جدول طويل إلى أسفل عموده، أحِل إليه في موضع لا تحمل فيه الصفحة التي يبدأ فيها أي عنصر عائم آخر (بعد صفحة شكل بعرض الصفحة مثلًا)، أو اجعل صفوفه تتسع في العمود.

يُبقي 'clip' الصفوف الأولى التي تتسع في الصفحة ويحذف الباقي بلا تنبيه (ومع ذلك تختم الملاحظة الجزء)؛ ويترك 'hide' الجدول كله. ولا يسري أيٌّ منهما إلا حين يكون الجدول أطول من صفحة: الجدول الذي يتسع يوضع كاملًا في أي وضع.

الجداول الموضوعة داخل النص. يتبع الجدول الموضوع في الموضع here (المضمّن بـ ::resource) القواعد نفسها منذ postext 1.25 (splitInline، المفعّل افتراضيًا). فإذا لم تتسع له المساحة الباقية في عموده قُطع بين الصفوف: يحتفظ الجزء الأول بفجوة العناصر العائمة فوقه وبصفوف رأس الجدول وصفّين من المتن على الأقل (وإن قلّ ذلك بدأ الجدول كله في العمود التالي، كما كان من قبل)، وكل جزء تالٍ يفتتح العمود التالي بلا فجوة فوقه، ويُنضَّد بعرض ذلك العمود (في تخطيط العمود ونصف يُنضَّد الجزء الذي يقع في العمود الضيق بعرضه)، ويأتي النص الذي يلي سطر ::resource بعد الجزء الأخير. أما تكرار رأس الجدول، والتعليق ذو اللاحقة، والعلامة، والملاحظة في الجزء الأخير، والصفوف الثلاثة التي لا يقلّ عنها الجزء الأخير، والقطوع التي تراعي الخلايا المدموجة ورؤوس المجموعات، فهي نفسها التي للجدول العائم. ولا يُقطع أبدًا جدول فيه أقل من خمسة صفوف في المتن. ويُبقي 'clip' الصفوف الأولى من الجدول المدرج الأطول من عمود، موضوعًا في رأس عمود؛ ويترك 'hide' مثل هذا الجدول كله؛ أما الجدول الأقصر فينتقل كاملًا إلى العمود التالي في الوضعين. والصفحة التي يفتتحها جدول مدرج لا تُبقي خاليةً من العناصر العائمة التي تستقبلها إلا مساحة جزئه الأول، فيتصدّر تلك الصفحةَ شكلٌ بعرض الصفحة كان ينتظرها ويستمر الجدول تحته. ولا تُقطع الجداول المدرجة في صفحة عمودية ولا الجداول داخل الإطار. وتنقل splitInline: false الجدول المدرج كاملًا إلى العمود التالي، كما كان حتى postext 1.24.

تتبع نصوص الاستمرار الافتراضية لغة المستند (locale، وإلا فلغة تقسيم الكلمات): في الإنجليزية (cont.) / Continued، وفي الإسبانية (cont.) / Continúa، وعلى المنوال نفسه في الفرنسية والألمانية والإيطالية والبرتغالية والكتالونية والهولندية (مذكورة تحت لغة المستند).

محتوى الخلية Markdown مضمّن، وفاصل السطر داخل الخلية، سواء كان سطرًا جديدًا أو \\ كما في التعليقات والملاحظات، يبدأ فقرة جديدة. والفقرة التي تبدأ بنقطة تعداد أو شرطة (•، -، *، –) أو برقم (1.، 1)) تليها مسافة تُنضَّد عنصرَ قائمة: تُرسم العلامة كما كُتبت، ويتعلّق النص بها بمسافة unorderedLists.gap الخاصة بالمستند، وتُحاذى الأسطر الملتفّة مع النص، ومسافتان في البداية تزيدان مستوى التداخل. فالخلية المكتوبة هكذا • Ofrece elección\n• Acomoda a personas diestras y zurdas تخرج قائمة من عنصرين. السطر الذي ليس فيه إلا مسافات عادية لا يضيف شيئًا؛ أما السطر الذي يضم مسافة غير منقسمة (U+00A0) فهو سطر من الخلية، كما في CommonMark، فـ 1\n تليها مسافة غير منقسمة تجعل الصف بارتفاع سطرين. والمسافة غير المنقسمة في آخر نص الخلية تحتفظ بعرضها: 760 تليها مسافة غير منقسمة، محاذاةً إلى اليمين فوق (231)، تنتهي قبل الحافة بمسافة، فيقترب الـ 0 من الـ 1. ولا تصطف الأرقام تمامًا إلا حيث تكون المسافة بعرض القوس، وهي في أغلب الخطوط أضيق منه. (حتى postext 1.4 كانت الاثنتان تُحذفان.)

عروض الأعمدة جزء من نموذج الجدول لا من النمط: TableModel.columnWidths مصفوفة اختيارية من الأوزان النسبية، وزن لكل عمود، تُطبَّع عند الإخراج، فـ [2, 1, 1] تعطي العمود الأول نصف العرض. وحين تغيب المصفوفة، أو يكون طولها خاطئًا، أو يكون أحد أوزانها غير موجب، يُقسم العرض بالتساوي. ويحافظ محرّر الجداول على توافق المصفوفة حين تُضاف أعمدة أو تُزال.

#بناء نماذج الجداول

TableModel شبكة مرتبة بالصفوف، وكل خلية تُخرَج بحسب موضعها فيها: rows[r][c] تقع في العمود c. لذلك تُبقي الخلية المدموجة الخلايا التي تغطيها في الشبكة، وكل منها معلَّمة بـ hiddenBy تشير إلى خليتها الأساسية، بخلاف جدول HTML الذي يحذفها. وتحافظ دوال النموذج المصدَّرة من postext على هذا الشكل؛ وهي دوال نقية تعيد نموذجًا جديدًا: mergeCells(model, { start, end }) وunmergeCell(model, at)، وaddRow، وaddColumn، وremoveRow، وremoveColumn، وsetCellContent، وsetCellImage، وsetCellBackground، وsetAlignment. والدوال الأربع الخاصة بالصفوف والأعمدة تُبقي الدمج سليمًا: الصف أو العمود المضاف داخل كتلة مدموجة يوسّعها، والمضاف قبلها يزيحها، والمُزال منها يقلّصها، والكتلة التي تفقد صفها أو عمودها الأول تحتفظ بمحتواها في خليتها العليا اليسرى الجديدة، وتبقى كل hiddenBy مشيرة إلى خليتها الأساسية.

تبني parseTSV(text, options?) نموذجًا من نص مفصول بعلامات الجدولة، أي نطاقًا ملصوقًا من جدول بيانات: تُقسم الصفوف عند فواصل الأسطر، والخلايا عند علامات الجدولة، وتُكمَّل الصفوف القصيرة لتكون الشبكة مستطيلة. ويحوّل headerRows الصفوف الأولى إلى صفوف رأس: تأخذ خلاياها isHeader ويأخذ النموذج headerRowCount، فيكررها الجدول المقسوم على عدة صفحات.

import { parseTSV, mergeCells } from 'postext';
 
let model = parseTSV('Part\tQty\tNote\nBolt\t4\tM6\nNut\t8\t', { headerRows: 1 });
// model.headerRowCount === 1; model.rows[0][0] is { content: 'Part', isHeader: true }
model = mergeCells(model, { start: { row: 2, col: 1 }, end: { row: 2, col: 2 } });
// rows[2][1] gets colSpan: 2; rows[2][2] stays in the grid with hiddenBy: { row: 2, col: 1 }

تفحص tableGridIssues(model) الشبكة. تعيد قائمة فارغة للنموذج السليم، وإلا فكل موضع تنكسر فيه الشبكة، بترتيب الصفوف: spanOverlap، أي خلية ظاهرة تحت colSpan / rowSpan لخلية أخرى (تسمّي coveredBy تلك الخلية)، وهو ما تفعله خلية مغطّاة حُذفت على طريقة HTML، لأن كل خلية بعدها تنزاح إلى موضع الدمج؛ وmissingCells، أي صف ينتهي قبل العمود الأخير دون دمج يغطي الباقي، فيترك فجوة.

import { tableGridIssues } from 'postext';
 
tableGridIssues({
  rows: [
    [{ content: 'A', colSpan: 2 }, { content: 'C' }],
    [{ content: '1' }, { content: '2' }, { content: '3' }],
  ],
});
// => [{ kind: 'spanOverlap', row: 0, col: 1, coveredBy: { row: 0, col: 0 } },
//     { kind: 'missingCells', row: 0, col: 2 }]

الجدول الذي يستخدمه المستند وفي شبكته مثل هذه المشكلات يُبلَّغ عنه في doc.contentWarnings بوصفه raggedTableGrid (انظر التحذيرات في المستند).

#نمط التعليقات

تتحكم الخاصية captionStyle في تعليقات الموارد (السطر Figure 1 — … تحت الصور ومخططات SVG والجداول، أو فوقها). تشترك التسمية المرقّمة والوصف في الخط والحجم نفسيهما، وهذا قيد في المحرّك، لكن يمكن أن يكون للتسمية وزنها وميلها ولونها الخاص. وحين لا تُحدَّد عائلة الخط وحجمه ولونه، تُورَث من نص المتن. يمكن أن يقع التعليق فوق المورد (العرف المعتاد في الجداول) وأن يُنضَّد على شريط ملوّن يمتد بعرض الكتلة؛ وتُنسَّق الملاحظة الاختيارية الأصغر (سطر المصدر، الحقوق: Resource.note) عبر الكائن الفرعي note. ويستطيع نوع المورد أن يتجاوز أيًّا من هذه الحقول لموارده عبر ResourceType.captionStyle (انظر أنواع الموارد).

const config: PostextConfig = {
  captionStyle: {
    align: 'center',
    labelBold: true,
    labelColor: { hex: '#295AA3', model: 'hex' },
    descriptionItalic: true,
    position: 'above',
    backgroundEnabled: true,
    padding: { value: 0.35, unit: 'em' },
    note: { italic: true, align: 'left' },
  },
};
الخاصيةالنوعالقيمة الافتراضيةالوصف
fontFamilystringخط نص المتنعائلة خط التعليق (التسمية والوصف).
fontSizeDimensionحجم نص المتنحجم خط التعليق (التسمية والوصف).
colorColorValueلون نص المتنلون نص الوصف.
align'left' | 'center' | 'right' | 'justify' | 'start' | 'end''left'المحاذاة الأفقية لأسطر التعليق. تمدّ 'justify' كل سطر إلى العرض الكامل ما عدا الأخير. وعلى شريط التعليق تُحاذى الأسطر داخل حشوته؛ والتعليق الجانبي يُحاذى داخل عرضه الخاص.
gapDimension0.75emالمسافة العمودية بين المورد وتعليقه.
labelBoldbooleantrueارسم التسمية المرقّمة (مثل Figure 1) بخط غامق.
labelItalicbooleanfalseارسم التسمية المرقّمة بخط مائل.
labelColorColorValuecolor التعليقلون التسمية المرقّمة.
descriptionItalicbooleanfalseارسم نص الوصف بخط مائل.
position'above' | 'below''below'موضع التعليق. مع 'above' يأتي التعليق (وشريطه) أولًا، وينزل جسم المورد بمقدار ارتفاع التعليق مضافًا إليه gap؛ وعندئذ توضع الملاحظة تحت الجسم.
backgroundEnabledbooleanfalseارسم شريطًا خلف التعليق. يمتد الشريط بعرض الكتلة كاملًا ويحيط بأسطر التعليق مع padding من كل جانب.
backgroundColorValueاللون الرئيسي في لوحة الألوانلون تعبئة الشريط (لا يُرسم إلا عند التفعيل).
paddingDimension0.35emالحشوة الداخلية بين حافة الشريط ونص التعليق. تُتجاهل حين يكون الشريط معطّلًا.
noteobject—تنسيق ملاحظة المورد؛ انظر الجدول الفرعي أدناه.
labelNumberGapstringمسافة غير منقسمة؛ و'' في المستند اليابانيما يفصل التسمية عن الرقم، في التعليق وفي :ref المضمّنة: Figure 1.7، Fig. 1.7. وفي الصينية واليابانية يتلاصقان: '' تعطي 图1-1، وهي القيمة الافتراضية في المستند الياباني (図1-1).
labelSeparatorstring'. '؛ و' ' في المستند اليابانيما يلي الرقم، قبل الوصف: Figure 1.7. A caption. والتعليقات الصينية تأخذ مسافة إيديوغرافية، ' ' (图1-1 标题)، وكذلك اليابانية افتراضيًا (図1-1 東京の地図، JLReq §4.3). والتسمية التي بلا رقم تحتفظ بقاعدتها الخاصة: نقطة، ما لم تنتهِ البادئة بنقطة.

ينسّق الكائن الفرعي note الحقل Resource.note، وهو نص قصير (المصدر، الحقوق، ملاحظة) يُنضَّد تحت المورد بحجم أصغر. يقبل التنسيق المضمّن نفسه وعلامات :ref نفسها التي يقبلها التعليق، ويرث خط التعليق. يوضع تحت التعليق حين يكون التعليق في الأسفل، وتحت جسم المورد حين يكون التعليق في الأعلى؛ ويُحسب ارتفاعه ضمن الكتلة، فالمورد ذو الملاحظة يطفو وحدةً واحدة.

الخاصيةالنوعالقيمة الافتراضيةالوصف
note.fontSizeDimension0.85 × حجم التعليقحجم خط الملاحظة.
note.colorColorValuecolor التعليقلون نص الملاحظة.
note.italicbooleanfalseارسم الملاحظة بخط مائل.
note.gapDimension0.35emالمسافة بين الملاحظة وما يسبقها (التعليق أو الجسم).
note.align'left' | 'center' | 'right' | 'justify' | 'start' | 'end''left'المحاذاة الأفقية لأسطر الملاحظة، كما في align للتعليق.

تُدمج التجاوزات الخاصة بكل نوع عبر mergeCaptionStyle(resolvedCaptionStyle, override, palette?)، وهي مصدَّرة للتطبيقات المضيفة التي تحتاج إلى الحسم نفسه خارج خط المعالجة.

#نمط المخططات

تتحكم الخاصية diagramStyle في تلوين مخططات SVG المضمّنة (موارد kind: 'svg') وفي الخطوط التي يُنضَّد بها نصها. وضع الحبر الواحد (single ink) تمريرة إعادة تلوين تحوّل كل لون في المخطط إلى درجة من حبر واحد، فتُستنسخ الأشكال بأمانة حين يُطبع المستند بلون خاص (spot colour) واحد. والخطوط المضمَّنة تضع في كل SVG أوجه الخطوط التي يسمّيها نصه، فتُنضَّد تسمياته بخطوط المستند على اللوحة (Canvas) وفي HTML وفي EPUB (انظر الخطوط في نصوص SVG).

const config: PostextConfig = {
  diagramStyle: {
    singleInk: true,
    inkColor: { hex: '#295AA3', model: 'hex' },
  },
};
الخاصيةالنوعالقيمة الافتراضيةالوصف
singleInkbooleanfalseأعد تلوين كل مخطط SVG مضمّن بدرجات من حبر واحد.
inkColorColorValueاللون الرئيسي (#295AA3)الحبر. قيمته الافتراضية هي اللون الرئيسي في لوحة ألوان المستند (مرتبط بلوحة الألوان عبر paletteId: 'main-color')، فتغيير عيّنة اللون في اللوحة يعيد تلوين المخططات مع العناوين والمقاطع الغامقة.
inlineFontsbooleantrueضمّن في كل SVG أوجه الخطوط التي يسمّيها نصه (font-family) في هيئة @font-face بعناوين data URI قبل عرضه صورةً: على اللوحة، وفي HTML، وفي EPUB، وفي الصورة النقطية البديلة في PDF. ولا يُكتب ذلك أبدًا في الملف المخزَّن. ويستثني المورد نفسه بـ svg.inlineFonts: false (منذ postext 1.25).

#كيف يعمل الحبر الواحد

حين يُفعَّل singleInk يُعاد كتابة كل لون في شيفرة SVG درجةً من inkColor شدتها 1 − الإضاءة النسبية (معاملات Rec. 709 مطبّقة على القنوات المرمّزة بغاما، وهو تقريب إدراكي يكفي تمامًا لتحويل الدرجات). يحافظ التحويل على القيمة المُدرَكة: الأبيض يصير بياض الورق، والأسود يصير الحبر كاملًا، والتعبئات الفاتحة تبقى فاتحة أيًّا كان لونها الأصلي. فالخلفية الصفراء الشاحبة تصير درجة شاحبة من الحبر، والخط الداكن يقترب من الحبر الكامل.

تتولى إعادة التلوين الدالة المصدَّرة applySingleInkToSvg(svgText, inkHex)، وتعمل على شيفرة SVG بوصفها نصًا دون DOM:

  • تُعاد كتابة القيم الست عشرية #rgb / #rgba / #rrggbb / #rrggbbaa، والدالتين rgb() / rgba()، والدالتين hsl() / hsla() أينما ظهرت: في سمات العرض، وفي style المضمّنة، وفي التدرجات، وفي <defs>. ويمكن أن تستخدم الدوال قنوات صحيحة أو عشرية أو نسبًا مئوية، وصيغة الفواصل أو صيغة المسافات، فيُعاد تلوين rgb(11.37%, 20%, 50.59%) (كما يكتبها Cairo) وrgb(51 102 153 / 50%) أيضًا. وتُكتب الدالة بعد تلوينها rgb(…) أو rgba(…).
  • لا تُستبدل الكلمتان white وblack إلا حيث تظهران قيمتَي طلاء (fill، stroke، stop-color، flood-color، color، سمات كانت أو خصائص في نمط مضمّن)، ولا تُستبدلان أبدًا داخل محتوى النص أو التسميات.
  • تُترك none وtransparent وcurrentColor دون مساس، وكذلك الألوان المسمّاة الأخرى (red، steelblue…) والأسود الافتراضي للشكل أو النص الذي لا يحدد تعبئة. أعطِ هذه العناصر لونًا صريحًا لكي يُعاد تلوينها.
  • تُحفظ قنوات الشفافية (أنصاف بايتات #rgba / #rrggbbaa ومكوّنات الشفافية في rgba(…) تمر دون تغيير؛ والشفافية المكتوبة بنسبة مئوية تُكتب رقمًا).
  • حين يتعذر تحليل inkHex تُعاد المدخلات دون تغيير.
  • تحمل النتيجة data-postext-single-ink="#…" (الحبر) على عنصرها الجذر <svg>، والشيفرة التي تحملها أصلًا تُعاد كما هي، أيًّا كان الحبر الذي تسمّيه. فالتحويل ليس متساوي القوى (idempotent): التمريرة الثانية تفتّح كل لون، فيخرج الأسود بنحو ثلثي الحبر، لذلك تُلوَّن الصورة مرة واحدة، أيًّا كان من يصل إليها أولًا: شيفرتك أو المُخرِجات. (العلامة جديدة في postext 1.5؛ والشيفرة التي لوّنها الإصدار 1.4 لا تحملها.)
import { applySingleInkToSvg } from 'postext';
 
const recoloured = applySingleInkToSvg(svgText, '#295AA3');
applySingleInkToSvg(recoloured, '#295AA3') === recoloured; // true: never twice

يسري الحبر الواحد في المُخرِجات الثلاثة: يعيد مُخرِج PDF تلوين بايتات SVG التي يسلّمها إليها resourceBytes قبل رسمها متجهات، ويلوّن مُخرِجا Canvas وHTML صور SVG التي يرسمانها حين تطلب منهما ذلك (انظر الحبر الواحد على اللوحة (Canvas) وفي HTML)، فيطابق ملف PDF المصدَّر المعاينةَ على الشاشة.

دالة الحسم ودالة الحذف تماثلان مثيلاتهما في الأقسام الأخرى، إلى جانب النوعين DiagramStyleConfig / ResolvedDiagramStyleConfig:

import {
  DEFAULT_DIAGRAM_STYLE_CONFIG,
  resolveDiagramStyleConfig,
  stripDiagramStyleDefaults,
  applySingleInkToSvg,
} from 'postext';
import type { DiagramStyleConfig, ResolvedDiagramStyleConfig } from 'postext';
 
const resolved = resolveDiagramStyleConfig(config.diagramStyle);
// => { singleInk: false, inkColor: { hex: '#295AA3', model: 'hex', paletteId: 'main-color' } }
 
const minimal  = stripDiagramStyleDefaults(config.diagramStyle);
// => undefined when everything matches the defaults

#الحبر الواحد على اللوحة (Canvas) وفي HTML

يتلقى مُخرِجا Canvas وHTML الصور مفكوكة الترميز مسبقًا (registerResourceImage) أو بوصفها عناوين URL (resourceImageUrl)، لا شيفرة SVG. وعند الطلب يطبّقان التحويل نفسه على ما يرسمانه:

  • Canvas (renderPage، renderPageToCanvas، renderToCanvas). كل صورة SVG يسري عليها التلوين، سواء كانت شكلًا أو صورة خلية في جدول أو صورة تصميم أو أيقونة إطار أو علامته، تُحوَّل إلى نقطيّة بحجمها الموضوع وتُلوَّن بكسلاتها بالحبر، سواء سُجّلت بوصفها <img> أو ImageBitmap. وتُخزَّن الصورة النقطية الملوّنة مؤقتًا كأي صورة نقطية لمتجهات. أما الصورة النقطية الأصلية فلا تُلوَّن أبدًا.
  • HTML (renderToHtml، renderToHtmlIndexed). تأخذ كل <img> من نوع SVG الخاصية filter: url(#pt-ink-…)، التي تشير إلى feColorMatrix تحمله صفحتها: عنصر <svg> بحجم صفري يضم <filter>، يوضع أولًا في الصفحة، وهو جزء من decorationHtml الخاص بالصفحة في المُخرَج المفهرس. وتحمله كل صفحة ما دام الحبر الواحد ساريًا، سواء ضمّت صورة أم لا، فالتطبيق المضيف الذي يرقّع الكتل واحدة واحدة لا يُدخل أبدًا صورة ينقصها مرشّحها.

لا تلوين مرتين. حتى postext 1.4 كان مُخرِجا Canvas وHTML يرسمان الصور كما تُعطى، فكانت التطبيقات المضيفة تعيد تلوين الشيفرة بنفسها بـ applySingleInkToSvg قبل تسليمها. وما زالت محوّلات الحزم وSandbox تفعل ذلك، لأن تمريرة الشيفرة تعطي ألوان PDF بالضبط (انظر الفقرة الأخيرة أدناه). لذلك تُلوَّن الصورة مرة واحدة، وفق ثلاث قواعد تسري في المُخرِجات الثلاثة:

  • الشيفرة المعلَّمة تُترك كما هي. يعيد مُخرِج PDF تلوين resourceBytes بـ applySingleInkToSvg، فبايتات SVG التي لُوّنت من قبل تُرسم كما هي. وفي Canvas وفي HTML، الصورة المحمّلة من عنوان SVG بصيغة data URI تحمل شيفرته العلامة لا تُلوَّن أبدًا كذلك.
  • معطّل ما لم يُطلب، في postext 1.x. لا يلوّن مُخرِج Canvas صورة SVG سُجّلت دون علامة خاصة بها إلا حين يمرّر الرسم singleInk: true (RenderPageOptions)، ويلوّن الصورة المسجّلة بـ registerResourceImage(id, img, { singleInk: true }) في أي رسم. ويلوّن مُخرِج HTML حين تتلقى renderToHtml القيمة singleInk: true، أو حين تحمل دالة الحسم resourceImageUrl الخاصة بها singleInk: true. والتطبيق المضيف المكتوب للإصدار 1.4، الذي يعيد تلوين الشيفرة ويسجّل الصورة المفكوكة دون علامة، يحتفظ بمُخرَجه. وسيكون التلوين افتراضيًا في الإصدار الرئيسي التالي.
  • singleInk: false لا يُلوَّن أبدًا. خلف عنوان blob أو عنوان شبكة لا يمكن قراءة الشيفرة من جديد، فالصورة التي أعدت تلوينها بنفسك وفككت ترميزها بهذه الطريقة تُسجَّل بـ singleInk: false، كما يفعل registerBundleImages وSandbox. وتعيد bundleImageUrl(bundle) دالة حسم تحمل singleInk: false، وتسلّم bundleResourceBytes ملف PDF بايتات الحزمة نفسها، فيعيد مُخرِج PDF تلوينها مرة واحدة.

يعرف المُخرِجان نوع كل صورة من الـ VDT: يحمل الشكل وصورة الخلية نوع موردهما، وتحمل كتلة صورة التصميم imageKind ('svg' أو 'bitmap')، المأخوذ من موردها عند الإخراج. أما الـ VDT المبني قبل وجود imageKind، فيعامل مُخرِج Canvas فيه صورة التصميم المسجّلة بوصفها مصدرًا متجهيًا على أنها SVG، ويعامل مُخرِج HTML بهذا الشكل الصورة التي عنوانها data URI من نوع SVG أو ينتهي بـ .svg.

إما أن تعيد تلوين الشيفرة بنفسك أو تترك المُخرِجات تلوّن الصورة الخام، لا الاثنين معًا:

import { applySingleInkToSvg, registerResourceImage, renderPage, renderToHtml } from 'postext';
 
// Raw SVG: tinted while diagramStyle.singleInk is on…
registerResourceImage('diagram.svg', rawImg, { singleInk: true });
// …or register it plainly and ask on each render.
registerResourceImage('diagram.svg', rawImg);
const canvas = renderPage(doc.pages[0], doc, { singleInk: true });
 
// Recoloured before decoding (as postext 1.4 hosts do): painted as given.
const inked = applySingleInkToSvg(svgText, ink);
registerResourceImage('diagram.svg', await decode(inked), { singleInk: false });
 
// The HTML backend with URLs to the raw markup.
const html = renderToHtml(doc, { resourceImageUrl: urlFor, singleInk: true });

تأخذ renderToHtml قيمة singleInk الافتراضية من العلامة الخاصة بدالة الحسم، فلا تحتاج bundleImageUrl(bundle) إلى أي خيار.

لكل لون تعيد تمريرة الشيفرة كتابته (القيم الست عشرية، وقيم rgb() وhsl()، وwhite وblack؛ انظر كيف يعمل الحبر الواحد)، يعطي تحويل البكسلات النتيجة نفسها، بما في ذلك الحواف المنعَّمة والتدرجات. ويختلفان حيث تترك تمريرة الشيفرة اللون كما هو: الألوان المسمّاة غير white وblack، وcurrentColor، والأشكال والنصوص التي بلا تعبئة (المرسومة بالأسود الافتراضي)، والصور النقطية المضمّنة في SVG، فهذه تُلوَّن على الشاشة وتحتفظ بلونها في PDF. أعطِ كل عنصر في المخطط لونًا صريحًا بصيغة ست عشرية أو rgb() أو hsl() لتحصل على مُخرَج متطابق. وحين لا تستطيع اللوحة قراءة البكسلات من جديد (<img> من أصل آخر، محمّلة دون CORS)، تُرسم الصورة دون تلوين.

#الخطوط في نصوص SVG

تُعرض صورة SVG عبر صورة: عنصر <img> على اللوحة وفي HTML وفي EPUB. ومستند الصورة لا يرى خطوط الويب في الصفحة، فيُنضَّد <text font-family="IBM Plex Sans"> بخط بديل من خطوط النظام. ومنذ postext 1.25 يضمّن المحرّك في الشيفرة أوجه الخطوط التي يسمّيها النص قبل فكّ ترميز الصورة أو تسليمها عنوانَ URL: قاعدة @font-face لكل ملف خط، وبايتاته في هيئة data URI، داخل <style> يلي وسم <svg> الجذر مباشرة. ولا يحتاج PDF إلى شيء من هذا: فهو ينضّد نص SVG نصًا حقيقيًا بخطوطه المضمَّنة (انظر بايتات الموارد والنسخ الأصلية للطباعة).

يُقرأ ما يطلبه النص من font-family وfont-weight وfont-style وfont، سماتٍ مستقلة وداخل سمات style وموروثةً من المجموعات المحيطة؛ وتُحتسب كذلك قاعدة <style> تسمّي عائلة. وتُستبعد العائلات العامة (serif وsans-serif…) والنص الذي في <title> أو <desc>، وتُترك العائلة التي يصرّح بها SVG نفسه بـ @font-face على حالها. ويمضي كل مقطع في قائمة font-family حتى أول عائلة لها وجه. ويأتي التلوين بالحبر الواحد أولًا، ثم الخطوط.

تأتي الأوجه من مزوّد له عقد PdfFontProvider في postext-pdf، فيخدم مزوّد واحد الطرفين: يُستدعى بالعائلة والوزن والنمط والحروف التي ينضّدها SVG بذلك الوجه، ويجيب بملف واحد أو بعدة ملفات. والعائلة المقدَّمة شرائحَ بحسب unicode-range (Fontsource وGoogle Fonts) يُجاب عنها بالشرائح التي تحتاجها تلك الحروف، فلا يحمل SVG ذو التسميات اللاتينية إلا ملف latin. ويقرأ المزوّد الافتراضي سجلّ الخطوط في المحرّك: تسجّل فيه loadBundleFonts أوجه الحزمة، ويسجّل المضيف أوجهه بـ registerFontBytes(family, weight, style, bytes, { unicodeRange })، أو بـ registerFontUrl(…) لملف يُجلب أول مرة يحتاجه فيها SVG. والعائلة التي لم يسجّلها شيء يُبحث عنها في قواعد @font-face من أوراق أنماط الصفحة القابلة للقراءة. أما FontFace المضاف إلى document.fonts من بايتات فلا يحتفظ بها، فلا يستطيع المحرّك قراءتها من جديد: سجّل هذه الأوجه أيضًا.

import { registerFontBytes, registerSvgImage, renderPage } from 'postext';
 
registerFontBytes('IBM Plex Sans', 700, 'normal', plexBoldWoff2);
await registerSvgImage('chart.svg', svgText);   // recoloured, fonts inlined, decoded, registered
const canvas = renderPage(doc.pages[0], doc);

أين يحدث ذلك:

  • اللوحة (Canvas). تعيد registerSvgImage(fileId, svgText, options) تلوين الصورة (inkHex)، وتضمّن خطوطها (fonts، وهو مزوّد؛ وinlineFonts: false تتخطى ذلك)، وتفكّ ترميزها وتسجّلها مصدرًا متجهيًا، وتُحَلّ بما آل إليه كل وجه. وتفعل registerBundleImages(bundle) الشيء نفسه بملفات SVG في الحزمة، بدءًا بأوجه الحزمة نفسها. وتعيد prepareSvgMarkup(svgText, options) الشيفرة المُعدّة للمضيف الذي يفكّ الترميز بنفسه.
  • HTML. تقدّم bundleImageUrl(bundle) شيفرة SVG وقد ضُمّنت فيها أوجه الحزمة. وتضمّن renderToHtml(doc, { inlineSvgFonts: true }) الخطوط في عناوين SVG من نوع data: التي تعيدها resourceImageUrl، من الأوجه التي يحفظها السجل في الذاكرة (أو inlineSvgFonts: { fonts, maxBytes, withhold }). ولا يمكن قراءة عنوان الكائن (object URL) بطريقة متزامنة، فالمضيف الذي يقدّم عناوين blob يضمّن الخطوط قبل أن ينشئها.
  • EPUB. تضمّن postext-epub الخطوط من fonts الخاصة بالكتاب، ثم من svgFonts.provider، قبل أن تكتب ملف SVG (انظر كتب EPUB).
  • PDF. يُنضَّد نص SVG نصًا حقيقيًا بالخطوط المضمَّنة. ولم يعد <style> لا يحوي إلا قواعد @font-face (أوجهًا ضمّنها المؤلف) يجعل الشكل يلجأ إلى صورة نقطية. وحين يلجأ الشكل إليها فعلًا (بسبب مرشّح أو تدرّج)، تُصنع صورته النقطية والأوجه مضمَّنة فيها من fontProvider الخاص بملف PDF.

والدوال الأدنى مستوى مصدَّرة أيضًا: تسرد svgFontRequests(svgText) عائلات كل مقطع ووزنه ونمطه وحروفه؛ وتعيد inlineSvgFonts(svgText, provider, options) وinlineSvgFontsSync(svgText, syncProvider, options) الشيفرة؛ وتضيف inlineSvgFontsDetailed تقريرًا عن كل وجه (inlined، declared، unavailable، withheld، tooLarge).

الخيارالنوعالقيمة الافتراضيةالوصف
maxBytesnumber2 MiBأقصى ما يُضمَّن من بايتات الخطوط في SVG واحد (قبل base64، الذي يزيدها الثلث). والأوجه التي تتجاوزه مجتمعةً لا يُضمَّن منها شيء، ويُبلَّغ عن svgFontsTooLarge.
formats('woff2' | 'woff' | 'ttf' | 'otf')[]الصيغ الأربعصيغ الملفات التي تُضمَّن؛ وتُتخطّى ملفات الصيغ الأخرى.
withhold(family) => booleanلا شيءالعائلات التي تُستبعد من ملف يغادر التطبيق (لا يجوز إعادة توزيعها). تبقى الإشارة إليها ويلجأ القارئ إلى خط بديل؛ ويُبلَّغ onWithheld(family) بكل واحدة منها.
onWarning(warning) => voidلا شيءيُبلَّغ بالعائلة التي لا وجه لها (svgFontUnavailable) وبتجاوز حد الحجم (svgFontsTooLarge).

الاستثناء. يترك diagramStyle.inlineFonts: false كل ملفات SVG كما خُزّنت؛ ويترك svg.inlineFonts: false على مورد ذلك المورد كما هو بايتًا ببايت، لملف SVG يحمل أوجهه بنفسه أو يجب ألا يتغيّر. وتكتب الحزمة استثناء المورد "inlineFonts": false في ملف preset.json الخاص بها.

التراخيص. يضع التضمين ملفات خطوط داخل صور قد تغادر التطبيق (تصدير HTML، ملف EPUB). ويحدث ذلك حين تُعرض الصورة أو تُصدَّر، لا في بايتات المورد المخزَّنة أبدًا، ويُبقي withhold خارج الملف العائلات التي لا يسمح ترخيصها بتمريرها إلى غيرك: يحجب كاتب EPUB الأوجه المعلَّمة بـ redistributable: false، ويحجب Sandbox العائلات المخصّصة المعلَّمة كذلك.

#نمط الفيديو

تحدّد الخاصية videoStyle كيف تُطبع موارد الفيديو، أي علامة التشغيل ورمز QR فوق الغلاف، وهل يكون الغلاف رابطًا إلى الفيديو، وما تقدّمه مشغّلاتها في عارض HTML وفي EPUB.

const config: PostextConfig = {
  videoStyle: {
    playMark: { shape: 'rounded', position: 'top-left', size: { value: 10, unit: 'mm' } },
    qr: { position: 'bottom-right', size: { value: 20, unit: 'mm' }, errorCorrection: 'Q' },
    player: { download: false, privacy: true },
  },
};
الخاصيةالنوعالقيمة الافتراضيةالوصف
playMarkVideoPlayMarkConfigانظر أدناهالعلامة المطبوعة على الغلاف للدلالة على أنه يُشغَّل.
qrVideoQrConfigانظر أدناهرمز QR المطبوع على الغلاف: يفتح صفحة الفيديو على يوتيوب أو فيميو، أو عنوان نشر الملف.
linkPosterbooleantrueاجعل الغلاف رابطًا إلى الفيديو: تعليق رابط (link annotation) فوقه في PDF، وعنصر <a> يحيط به في HTML وEPUB حيثما يُعرض الغلاف.
html'player' · 'poster''player'ما يضعه مُخرَج HTML مكان الفيديو: مشغّله، أو الغلاف المطبوع بعلامة التشغيل ورمز QR.
playerVideoPlayerOptionsانظر أدناهخيارات المشغّل لكل مقاطع الفيديو؛ وتُطبَّق فوقها video.player الخاصة بكل فيديو.

#علامة التشغيل

الخاصيةالنوعالقيمة الافتراضيةالوصف
enabledbooleantrueطباعة العلامة.
shape'circle' · 'rounded' · 'triangle''circle'قرص فيه مثلث، أو مستطيل مدوّر الزوايا فيه مثلث (عرضه 1.45 مرة قدر ارتفاعه)، أو المثلث وحده محدَّدًا بلون الخلفية.
positionVideoOverlayPosition'center''center'، أو زاوية ('top-left'، 'top-right'، 'bottom-left'، 'bottom-right')، أو منتصف أحد الجوانب ('top'، 'bottom'، 'left'، 'right'). المواضع مادية: الزاوية العليا اليمنى هي الزاوية العليا اليمنى في الكتاب الذي يُقرأ من اليمين إلى اليسار أيضًا.
sizeDimension12mmارتفاع العلامة؛ ولا يتجاوز أبدًا 40% من الضلع الأقصر للغلاف.
insetDimension4mmالمسافة من حواف الغلاف حين تقع العلامة في زاوية أو على جانب.
colorColorValueأبيضالمثلث.
backgroundColorValueاللون الرئيسيالقرص أو المستطيل خلف المثلث؛ وحدّ المثلث حين يكون وحده. مرتبط بلوحة الألوان افتراضيًا.
backgroundOpacitynumber0.9عتامة الخلفية، من 0 إلى 1.

#رمز QR

الخاصيةالنوعالقيمة الافتراضيةالوصف
enabledbooleantrueطباعة الرمز. والملف الذي بلا عنوان نشر لا يأخذ رمزًا.
positionVideoOverlayPosition'bottom-right'كما في علامة التشغيل. أعطِ الاثنين موضعين مختلفين.
sizeDimension18mmضلع الرمز مع هامشه؛ ولا يتجاوز أبدًا 45% من الضلع الأقصر للغلاف. تقرأ كاميرات الهواتف الوحدات التي لا تقل عن ثلث مليمتر: العنوان المؤلف من 30 حرفًا يعطي رمزًا من 29 وحدة، فبضلع 18 مم وهامش من وحدتين تكون الوحدة 0.55 مم.
insetDimension3mmالمسافة من حواف الغلاف.
errorCorrection'L' · 'M' · 'Q' · 'H''M'مقدار ما يمكن أن يتلف من الرمز أو يُغطّى ويبقى مقروءًا: نحو 7% أو 15% أو 25% أو 30%. ويُرفع تلقائيًا ما دام الرمز يحتفظ بعدد وحداته نفسه.
quietZonenumber2الوحدات الفاتحة حول الرمز على لوحته (من 0 إلى 8). فاللوحة تبرز من الغلاف، فلا حاجة إلى الوحدات الأربع التي يطلبها المعيار على الورق المكشوف.
colorColorValueأسودالوحدات الداكنة. أبقِها داكنة على لوحة فاتحة: فأغلب القارئات لا تقرأ الرموز المعكوسة.
backgroundColorValueأبيضاللوحة.
radiusDimension1mmنصف قطر زوايا اللوحة.

يرمّز المحرّك نفسه الرمز (encodeQr(text, level): وضع البايت، وUTF-8، والإصدارات من 1 إلى 40، والقناع ذو أدنى عقوبة) ويرسمه متجهات: تملأ اللوحة (Canvas) مسارًا واحدًا من سلاسل الوحدات، ويرسمه PDF باستدعاء drawSvgPath واحد، وHTML بعنصر <path> واحد مع shape-rendering="crispEdges"، فيبقى حادًّا بأي حجم طباعة.

#خيارات المشغّل

VideoPlayerOptions، في videoStyle.player وفي video.player لكل فيديو. يراعيها مشغّل HTML5 للملف كلها؛ ويراعي مشغّلا يوتيوب وفيميو ما تسمح به معاملات التضمين فيهما.

الخاصيةالقيمة الافتراضيةيراعيهاالوصف
controlstrueيوتيوب، فيميو، الملفاتإظهار أدوات التحكم في المشغّل.
downloadtrueالملفاتإتاحة زر التنزيل في المتصفح (controlslist="nodownload" عند إيقافه). يخفي الزر ولا يحمي الملف. ولا يتيح يوتيوب ولا فيميو التنزيل أبدًا.
fullscreentrueيوتيوب، فيميو، الملفاتإتاحة ملء الشاشة (fs=0، وallowfullscreen في iframe، وnofullscreen).
playbackRatetrueفيميو، الملفاتإتاحة قائمة السرعة (speed=0، noplaybackrate).
pictureInPicturetrueفيميو، الملفاتإتاحة صورة داخل صورة (pip=0، disablepictureinpicture).
remotePlaybacktrueالملفاتإتاحة الإرسال إلى شاشة أخرى (disableremoteplayback).
autoplayfalseيوتيوب، فيميو، الملفاتيبدأ التشغيل وحده، مكتوم الصوت دائمًا كما تشترط المتصفحات.
mutedfalseيوتيوب، فيميو، الملفاتيبدأ والصوت مُطفأ.
loopfalseيوتيوب، فيميو، الملفاتيُعاد من البداية عند الانتهاء.
exclusivetrueFolio، عارض HTML، EPUB (حين يشغّل النصوص البرمجية)؛ الملفاتتشغيل هذا الفيديو يوقف مؤقتًا المقاطع الأخرى المعروضة، فلا يعمل إلا واحد في كل مرة. وبالقيمة false يعمل إلى جانب غيره: المقاطع الصامتة المتكررة في صفحة، عدة منها في وقت واحد. منذ postext 1.18.
preload'metadata'الملفاتمقدار ما يحمّله المتصفح قبل التشغيل: 'none' أو 'metadata' أو 'auto'.
privacytrueيوتيوب، فيميوتضمين مع خصوصية معزَّزة: يوتيوب من youtube-nocookie.com، وفيميو مع dnt=1.

لا يحتفظ EPUB إلا بالسمات التي يعرفها مخططه: يُشغَّل الملف مع controls وautoplay وmuted وloop وplaysinline وpreload، ومعها العلامة data-pt-alongside، ويقرّر نظام القراءة الباقي.

الفيديو غير الحصري يحمل data-pt-alongside في مخرجات HTML. ويُلزم coordinateVideoPlayback(root) المشغّلات الواقعة تحت root (العنصر الذي يحوي مخرجات renderToHtml) بهذه القاعدة: تشغيل فيديو حصري يوقف مؤقتًا كل ما يعمل غيره، وتشغيل فيديو يعمل إلى جانب غيره يوقف المقاطع الحصرية وحدها. ويعيد دالة توقف الاستماع. ويعطي playsAlongside(el) وvideosToPause(started, videos, alongside) القاعدة نفسها لمضيف له مشغّلاته الخاصة. وفي Folio يبدأ الفيديو الذي يعمل تلقائيًا وإلى جانب غيره (autoplay مع exclusive: false) صامتًا كلما ظهرت صفحته، ويتوقف حين تُقلب الصفحة، وتعمل عدة مقاطع معًا، ويعيده loop من البداية عند انتهائه. وفي EPUB تربط الصفحةُ أو الفصلُ الذي فيه مقاطع تحتاج إلى تنسيق (مقطعان أو أكثر، أحدهما حصري) القاعدةَ نفسها في نص برمجي صغير، scripts/videos.js (التصدير VIDEO_PLAYBACK_SCRIPT)، وتعلن الحزمة ذلك المستند scripted. ونظام القراءة الذي يشغّل النصوص البرمجية يُلزم مقاطع ذلك المستند بالقاعدة، لا مقاطع الصفحة المقابلة لأنها مستند آخر؛ أما النظام الذي لا يشغّلها فيشغّل كل فيديو وفق قواعده، كما يفعل مشغّلا YouTube وVimeo دائمًا.

دالة الحسم ودالة الحذف تماثلان مثيلاتهما في الأقسام الأخرى، إلى جانب النوعين VideoStyleConfig / ResolvedVideoStyleConfig:

import {
  DEFAULT_VIDEO_STYLE_CONFIG,
  DEFAULT_VIDEO_PLAYER_OPTIONS,
  resolveVideoStyleConfig,
  resolveVideoPlayerOptions,
  stripVideoStyleDefaults,
} from 'postext';
 
const resolved = resolveVideoStyleConfig(config.videoStyle);
const player = resolveVideoPlayerOptions(resource.video?.player, resolved.player);
const minimal = stripVideoStyleDefaults(config.videoStyle); // undefined when all defaults