باختصار
حين يتوقف عمود قبل أسفل الصفحة بسطر لأن فقرة لا يجوز قطعها هناك، يعيد هذا الإعداد تقسيم أسطر فقرة من ذلك العمود حتى يبلغ الأسفل.
ما الذي ستنضده
ثلاث صفحات من مجلة ساحلية اسمها Littoral: مقال طويل عن عدسة فرينل يفتتح بلوحة لعدسة مضاءة في غرفة الفانوس. يجري النص في عمودين بخط Public Sans بمحاذاة إلى اليسار غير مضبوطة، وبين الفقرات سطر فارغ من الشبكة، وتُبقي قواعد الأسطر الأرملة واليتيمة سطرين على الأقل من الفقرة على كل جانب من القطع. ينضد القلم المقال مرتين. من دون الإعداد، ينتهي العمود الأيسر في صفحة الافتتاح قبل الأيمن بسطر، لأن الفقرة التالية لا يجوز أن تبدأ بسطر واحد في أسفل العمود. ومع الإعداد، في الصفحة نفسها، تمتد الفقرة الأولى سطرًا إضافيًا وينتهي العمودان على آخر سطر في الشبكة. وفي الصفحة التالية يستعيد الإعداد أيضًا الفراغ الزائد الذي وضعته الموازنة فوق عنوانين فرعيين.
تجيب هذه الوصفة عن
- لماذا ينتهي العمود قبل أسفل الصفحة بسطر أو سطرين، وكيف أملؤه دون خرق قواعد الأسطر اليتيمة والأرملة؟
- كيف أجعل أسافل الأعمدة متساوية وأوازن الصفحة الأخيرة (الضبط العمودي)؟
- كيف أتجنب الأسطر الأرامل والأسطر اليتيمة والأسطر الأخيرة ذات الكلمة الواحدة، وأبقي العنوان مع نصه؟
الجواب المختصر
const balancing = {
// A ragged column has no lever of its own when the widow and orphan rules hold its last
// line back: no heading or list end to open, no justified paragraph to set looser. This
// one lets a paragraph of the short column run a line long, within its own word spacing
// (or a justified one no looser than maxWordSpacing), or, when none can, set one a line
// short so the split after it takes the line. A change is kept only when the pages end
// with fewer short columns; otherwise the column stays as it was.
fillShortColumns: true, // default false
// The paragraphs go first: a crosshead keeps its one line of air unless they cannot help.
maxLinesBeforeLoose: 0,
};
const bodyText = {
fontFamily: 'Public Sans', fontSize: pt(9.4), lineHeight: pt(LEAD), color: col('ink'),
boldColor: col('ink'), italicColor: col('ink'), referenceColor: col('ink'),
textAlign: 'left', firstLineIndent: pt(0), paragraphSpacing: true, // a grid line between
hyphenation: { enabled: true }, optimalLineBreaking: true, optimalRagged: true,
avoidWidows: true, widowMinLines: 2, avoidOrphans: true, orphanMinLines: 2, // the cause
avoidRunts: true,
};
المكونات
- الميزات
- ملء الأعمدة القصيرةموازنة الأعمدةالأسطر الأرامل والأسطر اليتيمة والكلمات المعزولةالتقسيم الأمثل للأسطر (Knuth–Plass)صفحات افتتاح مصمَّمةالصور في تصاميم الصفحةسمات العنوانالترويسات وأرقام الصفحاتالترويسات بحسب دور الصفحةلوحة ألوان دلاليةأنماط الفقرات
- الخطوط
- Public Sans, Libre Caslon Display, Libre Caslon Text (SIL OFL 1.1)
- الأصول
lens-1400.jpg- A lit Fresnel lens in a lighthouse lantern at dusk (Generated With Diffusion Models, أصلي)
طريقة التحضير
#1 · أعطِ العمود غير المضبوط وسيلة خاصة به
const balancing = {
// A ragged column has no lever of its own when the widow and orphan rules hold its last
// line back: no heading or list end to open, no justified paragraph to set looser. This
// one lets a paragraph of the short column run a line long, within its own word spacing
// (or a justified one no looser than maxWordSpacing), or, when none can, set one a line
// short so the split after it takes the line. A change is kept only when the pages end
// with fewer short columns; otherwise the column stays as it was.
fillShortColumns: true, // default false
// The paragraphs go first: a crosshead keeps its one line of air unless they cannot help.
maxLinesBeforeLoose: 0,
};
const bodyText = {
fontFamily: 'Public Sans', fontSize: pt(9.4), lineHeight: pt(LEAD), color: col('ink'),
boldColor: col('ink'), italicColor: col('ink'), referenceColor: col('ink'),
textAlign: 'left', firstLineIndent: pt(0), paragraphSpacing: true, // a grid line between
hyphenation: { enabled: true }, optimalLineBreaking: true, optimalRagged: true,
avoidWidows: true, widowMinLines: 2, avoidOrphans: true, orphanMinLines: 2, // the cause
avoidRunts: true,
};
تملأ موازنة الأعمدة العمود القصير بإضافة أسطر من الشبكة فوق عنوان، أو بعد قائمة، أو تحت إطار أو شكل، وفي آخر الأمر بنضد فقرة مضبوطة المحاذاة أوسع بسطر. أما عمود النص الجاري غير المضبوط فليس فيه شيء من ذلك بين عناوينه الفرعية، فيبقى السطر الذي تحجزه قواعد الأسطر الأرملة واليتيمة فارغًا. يسمح fillShortColumns لفقرة غير مضبوطة من ذلك العمود بأن تمتد سطرًا إضافيًا بمسافاتها نفسها بين الكلمات؛ فإن لم تستطع أي فقرة ذلك، نُضدت إحداها أقصر بسطر لتصعد الفقرة التي بعد القطع وتملأ السطر. ولا يحتفظ المحرك بالتغيير إلا إذا قلّ عدد الأعمدة القصيرة، فتفعيله لا يجعل أي صفحة أسوأ. ويجرّب maxLinesBeforeLoose: 0 الفقرات قبل العناوين، فيحتفظ كل عنوان فرعي بسطر واحد من الفراغ فوقه.
#2 · انضد المقال مرتين وقارن
const withFill = (fill) => ({ ...config, headings: { ...config.headings,
balancing: { ...balancing, fillShortColumns: fill } } }); // a copy: config keeps true
const build = (fill) => buildDocumentWithFonts({ markdown, resources }, withFill(fill),
kitFonts(FONTS));
const docs = { off: await build(false), on: await build(true) };
يخرج المستندان من الإعدادات نفسها ونص Markdown نفسه. يمرر النضد الثاني الإعدادات كما هي، ويمرر الأول نسخة أُطفئ فيها الإعداد، فتحمل الحزمة التي تُفتح في Sandbox القيمة fillShortColumns: true. ويبدّل الزران فوق المكتب بين النضدين. ولا تُملأ حتى الأسفل إلا الصفحات التي يستمر بعدها النص: الصفحة 3 تختم المقال، وتُقطع أعمدتها على ارتفاع واحد في النضدين.
#3 · افتتح باللوحة
const DEPTH = 150; // mm: how far down the page the painting bleeds
const sans = { fontFamily: 'Public Sans', fontWeight: 700, textTransform: 'uppercase' };
const at = (to, edge, x, y, width) => ({ anchor: { to, edge },
offset: { x: mm(x), y: mm(y) }, ...(width && { size: { width: mm(width) } }) });
const air = { padding: { bottom: mm(6) } }; // counts: the text starts 6 mm under the byline
const opener = {
enabled: true,
slot: { elements: [
{ kind: 'image', id: 'art', resourceId: 'lens', placement: { anchor: { to: 'bleed',
edge: 'top-left' }, size: { width: 'fill', height: mm(DEPTH) } } },
{ kind: 'text', id: 'credit', content: '{attr.credit}', ...sans, fontWeight: 500,
fontSize: pt(6.5), letterSpacing: pt(0.6), color: col('muted'), align: 'right',
placement: at('container', 'top-right', 0, DEPTH - TOP + 2) },
{ kind: 'text', id: 'kicker', content: '{attr.kicker}', ...sans, fontSize: pt(8.5),
letterSpacing: pt(1.7), color: col('brass'), align: 'left',
placement: at('container', 'top-left', 0, DEPTH - TOP + 9) },
{ kind: 'text', id: 'headline', content: '{titleText}', fontFamily: 'Libre Caslon Display',
fontSize: pt(46), lineHeight: 1.02, color: col('ink'), align: 'left',
placement: at('#kicker', 'below', 0, 2.5, 112) },
{ kind: 'text', id: 'standfirst', content: '{attr.standfirst}', fontFamily: 'Libre Caslon Text',
italic: true, fontSize: pt(12), lineHeight: 1.3, color: col('sea'), align: 'left',
placement: at('#headline', 'right-of', 8, 2.5) },
{ kind: 'text', id: 'byline', content: '{attr.byline}', ...sans, fontSize: pt(7.5),
letterSpacing: pt(1.3), color: col('ink'), align: 'left', box: air,
placement: at('#standfirst', 'below', 0, 3.5) },
] },
};
اللوحة عنصر صورة مثبت على حافة النزف بارتفاع 150 مم، فتبلغ حد القص من ثلاث جهات، وهذا ما لا يفعله أي عنصر عائم. ويأتي العنوان التمهيدي والعنوان والمقدمة واسم الكاتب من سمات سطر #. وينتهي اسم الكاتب بحشوة مقدارها 6 مم تُحسب في ارتفاع الافتتاح، فتبدأ الأعمدة على أول سطر من الشبكة تحته.
#4 · ترويسات خارج صفحة الافتتاح
const label = { ...sans, fontWeight: 600, fontSize: pt(7.5), letterSpacing: pt(1.3),
color: col('muted') };
const folio = { fontFamily: 'Public Sans', fontWeight: 700, fontSize: pt(8.5), color: col('ink') };
const pin = (edge, x, y) => ({ anchor: { to: 'page', edge }, offset: { x: mm(x), y: mm(y) } });
const sides = [['even', 'left', 1], ['odd', 'right', -1]]; // 1: the outer edge is on the left
const header = { elements: sides.flatMap(([parity, edge, s]) => [
{ kind: 'text', id: `folio-${parity}`, content: '{pageNumber}', ...folio,
placement: pin(`top-${edge}`, s * OUTER, 12) },
{ kind: 'text', id: `head-${parity}`, ...label,
content: s > 0 ? '{title} · {subtitle}' : '{chapterTitle}',
placement: pin(`top-${edge}`, s * (OUTER + 9), 12.35) }, // the smaller size's baseline
{ kind: 'rule', id: `rule-${parity}`, direction: 'horizontal', thickness: pt(0.5),
color: col('rule'), placement: { ...pin(`top-${edge}`, s * OUTER, 17),
size: { width: mm(197) } } },
].map((element) => ({ ...element, parity, pages: 'body' }))) }; // none on the opener
const footer = { elements: sides.map(([parity, edge, s]) => ({ kind: 'text', parity,
id: `drop-folio-${parity}`, ...folio, content: '{pageNumber}', pages: 'opener', align: edge,
placement: pin(`bottom-${edge}`, s * OUTER, -11) })) }; // the opener's only folio
تذكر الصفحة الزوجية اسم المجلة والعدد من الـ frontmatter، والفردية عنوان المقال، ويُبعد pages: 'body' الاثنين عن صفحة الافتتاح التي لا تحمل إلا رقم صفحة في أسفلها. وتنزل التسمية 0.35 مم عن رقم الصفحة ليتشارك الحجمان خط أساس واحدًا. والخط الرفيع تحتهما طوله 197 مم، أي عرض كتلة النص.
الوصفة كاملة
// ═══ Postext Cookbook · Nº 177 · Filling the column the widow rule left short ═════
// https://postext.dev/en/cookbook/filling-short-columns
// Code: MIT · Text: original (CC BY 4.0) · Painting: diffusion model
// Fonts: Public Sans, Libre Caslon Display, Libre Caslon Text (OFL) · Needs postext ≥ 1.65.0
//
// A magazine long read in two ragged columns, set twice. Without fillShortColumns a column
// whose paragraph would split into a widow ends a line above the foot; with it, a paragraph
// of that column is set a line longer or shorter, and the column ends on the last grid line.
import {
buildDocumentWithFonts, renderPageToCanvas, registerResourceImage,
} from 'https://esm.sh/postext';
const LANG = 'en'; // @lang: the language of the sample document ('en' | 'es')
const RECIPE = 'filling-short-columns';
// ─── 1 · Design ─────────────────────────────────────────────────────────────
// Every colour below is linked to this palette by id.
const palette = {
ink: '#1b2026', // text: a slate near-black
brass: '#875a14', // the accent: kicker and crossheads (6:1 on paper)
sea: '#2c4a5e', // the standfirst
rule: '#c9ccd0', // hairlines
muted: '#5d656e', // running heads, credit, colophon
paper: '#ffffff',
};
const col = (id) => ({ hex: palette[id], model: 'hex', paletteId: id });
const colorPalette = [
...Object.entries(palette).map(([id, hex]) => ({ id, name: id, value: { hex, model: 'hex' } })),
// The engine's defaults link to 'main-color': point it at the accent, so nothing prints blue.
{ id: 'main-color', name: 'brass (defaults)', value: { hex: palette.brass, model: 'hex' } },
];
const TOP = 22; // top margin in mm: the opener's container starts here
const OUTER = 14; // outer margin in mm: folios and running heads align to it
const LEAD = 13.2; // body leading in pt: the baseline grid every column ends on
// #region answer: ragged columns that end on the grid: fillShortColumns
const balancing = {
// A ragged column has no lever of its own when the widow and orphan rules hold its last
// line back: no heading or list end to open, no justified paragraph to set looser. This
// one lets a paragraph of the short column run a line long, within its own word spacing
// (or a justified one no looser than maxWordSpacing), or, when none can, set one a line
// short so the split after it takes the line. A change is kept only when the pages end
// with fewer short columns; otherwise the column stays as it was.
fillShortColumns: true, // default false
// The paragraphs go first: a crosshead keeps its one line of air unless they cannot help.
maxLinesBeforeLoose: 0,
};
const bodyText = {
يعمل ملف script.js المجمّع كما هو: الصقه في سكربت الوحدة (module) لأي صفحة، أو افتح الوصفة على CodePen. مجلد الوصفة على GitHub ↗ (يفتح في تبويب جديد)
تنويعات
#دع العناوين الفرعية تأخذ الفراغ أولًا
في الترتيب الافتراضي يأخذ العنوان سطرين من الشبكة على الأكثر قبل أن تُطال فقرة، فتتسع من جديد المسافة فوق العناوين الفرعية في الصفحة التالية.
- maxLinesBeforeLoose: 0,
+ maxLinesBeforeLoose: 2,#اضبط محاذاة النص
كانت الفقرة المضبوطة تستطيع من دون الإعداد أن تمتد سطرًا إضافيًا في حدود maxWordSpacing. ومع fillShortColumns تستطيع أيضًا أن تقصر سطرًا، ولا تُنضد أبدًا أوسع من أوسع أسطرها.
- textAlign: 'left', firstLineIndent: pt(0), paragraphSpacing: true, // a grid line between
+ textAlign: 'justify', firstLineIndent: pt(0), paragraphSpacing: true, // a grid line betweenأخطاء شائعة
- يسد الإعداد فراغًا من سطر واحد. أما العمود الذي ينقصه سطران، كالذي ينتهي بعد المسافة بين فقرتين ولا يبقى فيه مكان إلا لسطر واحد من الفقرة التالية، فقد يبقى قصيرًا: عدّل النص بجملة.
- لا تُستعمل فقرة ينتهي نضدها الجديد بسطر أخير قصير جدًا، لذلك قد يبقى العمود قصيرًا إذا لم تكن فيه إلا فقرات قصيرة.
الحقوق
- الوصفة
- Ignacio Ferro
- النص
- نص أصلي, CC BY 4.0
- الصور
- A lit Fresnel lens in a lighthouse lantern at dusk · Generated With Diffusion Models · أصلي
- الخطوط
- Public Sans (SIL OFL 1.1) · Libre Caslon Display (SIL OFL 1.1) · Libre Caslon Text (SIL OFL 1.1)
- الشيفرة
- MIT، مثل Postext
حرّر هذا الشرح ↗ (يفتح في تبويب جديد)مجلد الوصفة على GitHub ↗ (يفتح في تبويب جديد)


