ویکینبشته
fawikisource
https://fa.wikisource.org/wiki/%D8%B5%D9%81%D8%AD%D9%87%D9%94_%D8%A7%D8%B5%D9%84%DB%8C
MediaWiki 1.47.0-wmf.24
first-letter
مدیا
ویژه
بحث
کاربر
بحث کاربر
ویکینبشته
بحث ویکینبشته
پرونده
بحث پرونده
مدیاویکی
بحث مدیاویکی
الگو
بحث الگو
راهنما
بحث راهنما
رده
بحث رده
درگاه
بحث درگاه
پدیدآورنده
بحث پدیدآورنده
برگه
گفتگوی برگه
فهرست
گفتگوی فهرست
TimedText
TimedText talk
پودمان
بحث پودمان
Event
Event talk
برگه:The Blind Owl.pdf/۸۵
104
46727
305351
299273
2026-10-25T13:52:17Z
Charan Gill
6444
305351
proofread-page
text/x-wiki
<noinclude><pagequality level="3" user="Hanooz" />{{سرصفحهمستمر|۸۵||}}</noinclude>در آمده بودند. خورشید مثل چشم تب دار، پرتو سوزان خود را از ته آسمان نثار منظره خاموش و بیجان میکرد ولی خاک و گیاههای اینجا بوی مخصوصی داشت، بوی آن بقدری قوی بود که از استشمام آن به یاد دقیقههای بچگی خودم افتادم - نه تنها حرکات و کلمات آنزمان را در خاطرم مجسم کرد، بلکه یک لحظه آن دوره را در خودم حس کردم، مثل اینکه دیروز اتفاق افتاده بود، یکنوع سرگیجه گوارا بمن دست داد، مثل اینکه دوباره در دنیای گمشدهای متولد شده بودم. این احساس یک خاصیت مست کننده داشت و مانند شراب کهنه شیرین در رگ و پی من، تا ته وجودم تاثیر میکرد – در صحرا خارها، سنگها، تنه درختها و بتههای کوچک {{w|کاکوتی}} را میشناختم - بوی خودمانی سبزهها را میشناختم - یاد روزهای دوردست خودم افتادم ولی همه این یادبودها بطرز افسون مانندی از من دور شده بود و آن یادگارها با هم زندگی مستقلی داشتند، در صورتیکه من شاهد دور و بیچارهای بیش نبودم و حس میکردم که میان من و آنها گرداب عمیقی کنده شده بود، حس میکردم که امروز دلم تهی و بتههای عطر جادوئی آنزمان را گم کرده بودند، درختهای سرو بیشتر فاصله پیدا کرده بودند، تپهها خشکتر شده بودند - موجودی<noinclude></noinclude>
igb0t23lxcrilbrrjjsk9jxlzpcj42k
305352
305351
2026-10-25T14:23:02Z
Charan Gill
6444
/* همسنجیشده */
305352
proofread-page
text/x-wiki
<noinclude><pagequality level="4" user="Charan Gill" />{{سرصفحهمستمر|۸۵||}}</noinclude>در آمده بودند. خورشید مثل چشم تب دار، پرتو سوزان خود را از ته آسمان نثار منظره خاموش و بیجان میکرد ولی خاک و گیاههای اینجا بوی مخصوصی داشت، بوی آن بقدری قوی بود که از استشمام آن به یاد دقیقههای بچگی خودم افتادم - نه تنها حرکات و کلمات آنزمان را در خاطرم مجسم کرد، بلکه یک لحظه آن دوره را در خودم حس کردم، مثل اینکه دیروز اتفاق افتاده بود، یکنوع سرگیجه گوارا بمن دست داد، مثل اینکه دوباره در دنیای گمشدهای متولد شده بودم. این احساس یک خاصیت مست کننده داشت و مانند شراب کهنه شیرین در رگ و پی من، تا ته وجودم تاثیر میکرد – در صحرا خارها، سنگها، تنه درختها و بتههای کوچک {{w|کاکوتی}} را میشناختم - بوی خودمانی سبزهها را میشناختم - یاد روزهای دوردست خودم افتادم ولی همه این یادبودها بطرز افسون مانندی از من دور شده بود و آن یادگارها با هم زندگی مستقلی داشتند، در صورتیکه من شاهد دور و بیچارهای بیش نبودم و حس میکردم که میان من و آنها گرداب عمیقی کنده شده بود، حس میکردم که امروز دلم تهی و بتههای عطر جادوئی آنزمان را گم کرده بودند، درختهای سرو بیشتر فاصله پیدا کرده بودند، تپهها خشکتر شده بودند - موجودی<noinclude></noinclude>
ijrqy5jckuuo2u4p37q2c3xsl8dmmzf
برگه:The Blind Owl.pdf/۸۷
104
46730
305353
299399
2026-10-25T14:26:14Z
Charan Gill
6444
/* همسنجیشده */
305353
proofread-page
text/x-wiki
<noinclude><pagequality level="4" user="Charan Gill" />{{سرصفحهمستمر|۸۷||}}</noinclude>نهر سورن زیر سایه یک درخت کهن سرو روی ماسه نشستم، جای خلوت و دنجی بود، بنظر میامد که تا حالا کسی پایش را اینجا نگذاشته بود. ناگهان ملتفت شدم دیدم از پشت درختهای سرو یک دختر بچه بیرون آمد و بطرف قلعه رفت، لباس سیاهی داشت که با تاروپود خیلی نازک و سبک گویا با ابریشم بافته شده بود، ناخن دست چپش را میجوید و با حرکت آزادانه و بی اعتنا میلغزید و رد میشد. بنظرم آمد که من او را دیده بودم و میشناختم ولی از این فاصله دور زیر پرتو خورشید نتوانستم تشخیص بدهم که چه طور یکمرتبه ناپدید شد.
«من سر جای خودم خشکم زده بود بی آنکه بتوانم کمترین حرکتی بکنم ولی ایندفعه با چشمهای جسمانی خودم او را دیدم که از جلو من گذشت و ناپدید شد. آیا او موجودی حقیقی و یا یک وهم بود؟ آیا خواب دیده بودم و یا در بیداری بود؟ هرچه کوشش کردم که یادم بیاید بیهوده بود - لرزه مخصوصی روی تیره پشتم حس کردم بنظرم آمد که درین ساعت همه سایههای قلعه روی کوه جان گرفته بودند و آن دخترک یکی از ساکنین سابق شهر قدیم ری بوده.
«منظرهای که جلو من بود یکمرتبه به نظرم آشنا آمد، در بچگی یک روز سیزده بدر یادم افتاد که همینجا آمده بودم، مادرزنم و آن لکاته هم بودند - ما چقدر آنروز<noinclude></noinclude>
o62yhk1h7lrtxccuf7f9bljp8r884pi
برگه:The Blind Owl.pdf/۸۸
104
46731
305354
299276
2026-10-25T15:08:03Z
Charan Gill
6444
/* همسنجیشده */
305354
proofread-page
text/x-wiki
<noinclude><pagequality level="4" user="Charan Gill" />{{سرصفحهمستمر|۸۸||}}</noinclude>پشت همین درختهای سرو دنبال یکدیگر دویدیم و بازی کردیم، بعد یک دسته از بچههای دیگر هم بما ملحق شدند که درست یادم نیست. سرمامک بازی میکردیم، یکمرتبه که من دنبال همین لکاته رفتم نزدیک همین نهر سورن بود پای او لغزید و در نهر افتاد - او را بیرون آوردند، بردند پشت درخت سرو رختش را عوض بکنند من هم دنبالش رفتم، جلو او چادر نماز گرفته بودند اما من دزدکی از پشت درخت تمام تنش را دیدم. او لبخند میزد و انگشت سبابه دست چپش را میجوید، بعد یک رودوشی سفید به تنش پیچیدند و لباس سیاه ابریشمی او را که از تار و پود نازک بافته شده بود جلو آفتاب پهن کردند.
«بالاخره پای درخت کهن سرو روی ماسه دراز کشیدم، صدای آب مانند حرفهای بریده بریده و نامفهومی که در عالم خواب زمزمه میکنند بگوشم میرسید، دستهایم را بی اختیار در ماسه گرم و نمناک فرو بردم، ماسه گرم نمناک را در مشتم میفشردم، مثل گوشت سفت تن دختری بود که در آب افتاده باشد و لباسش را عوض کرده باشند.
«نمیدانم چقدر وقت گذشت، وقتیکه از سر جای خودم بلند شدم بی اراده براه افتادم، همه جا ساکت و آرام بود، من میرفتم ولی اطراف خودم را نمیدیدیم، یک قوهای که به اراده من نبود مرا وادار برفتن میکرد، همه حواسم<noinclude></noinclude>
qpn8tu7kramke1dr1huyqxb4p4hnqo4
برگه:The Blind Owl.pdf/۸۹
104
46733
305355
299277
2026-10-26T07:37:05Z
Charan Gill
6444
/* همسنجیشده */
305355
proofread-page
text/x-wiki
<noinclude><pagequality level="4" user="Charan Gill" />{{سرصفحهمستمر|۸۹||}}</noinclude>متوجه قدمهای خودم بود من راه نمیرفتم ولی مثل آن دختر سیاهپوش روی پاهایم میلغزیدم و رد میشدم. همینکه بخودم آمدم دیدم در شهر و جلو خانه پدرزنم هستم - نمیدانم چرا گذارم بخانه پدرزنم افتاد - پسر کوچکش، برادرزنم - روی سکو نشسته بود - مثل سیبی که با خواهرش نصف کرده باشند: چشمهای مورب ترکمنی، گونه های برجسته،<!-- ؟ --> رنگ گندمی، دماغ شهوتی، صورت لاغر ورزیده داشت و همینطور که نشسته بود انگشت سبابه دست چپش را بدهنش گذاشته بود، من بی اختیار جلو رفتم، دست کردم کلوچههائی که در جیبم بود درآوردم باو دادم و گفتم: «اینا رو شاجون برات داده» چون بزن من بجای مادر خودش شاهجان میگفت - او با چشمهای ترکمنی خود نگاه تعجب آمیزی به کلوچهها کرد که با تردید در دستش گرفته بود. من روی سکوی خانه نشستم، او را در بغلم نشاندم و بخودم فشار دادم. تنش گرم و ساق پاهایش شبیه ساق پاهای زنم بود و همان حرکات بی تکلف او را داشت. لبهای او شبیه لبهای پدرش بود - اما آنچه که نزد پدرش مرا متنفر میکرد برعکس در او برای من جذبه و کشندگی داشت - مثل این بود که لبهای نیمه باز او تازه از یک بوسه گرم طولانی جدا شده - روی دهن نیمه بازش را بوسیدم که<noinclude></noinclude>
c0hkv383fjwjg9zd72uitdec7io4lfq
ویکینبشته:GUS2Wiki
4
61039
305356
305244
2026-10-26T11:07:21Z
Alexis Jazz
27824
Updating gadget usage statistics from [[Special:GadgetUsage]] ([[phab:T121049]])
305356
wikitext
text/x-wiki
{{#ifexist:Project:GUS2Wiki/top|{{/top}}|This page provides a historical record of [[Special:GadgetUsage]] through its page history. To get the data in CSV format, see wikitext. To customize this message or add categories, create [[/top]].}}
دادههای زیر در حافظهٔ نهانی وجود دارند و آخرین بار در 2026-10-25T06:27:44Z روزآمدسازی شدهاند. حداکثر {{PLURAL:5000|یک نتیجه|5000 نتیجه}} در حافظۀ نهان موجود است.
{| class="sortable wikitable"
! ابزار !! data-sort-type="number" | شمار کاربران !! data-sort-type="number" | کاربران فعال
|-
|AHK || 16 || 2
|-
|BotTools || 15 || 3
|-
|CleanDeleteReasons || 21 || 1
|-
|CustomizedEditTool || 17 || 3
|-
|GoogleTranslator || 30 || 2
|-
|GreenRedirect || 15 || 2
|-
|HotCat || 54 || 4
|-
|ImageAnnotator || 35 || 1
|-
|InterwikiTranslate || 31 || 0
|-
|UserMessages || 15 || 2
|-
|VeExtendedBar || 17 || 2
|-
|VeSuperTool || 18 || 2
|-
|addon-buttonsformove || 36 || 2
|-
|defaultsummaries || 20 || 1
|-
|dropdown-menus || 19 || 3
|-
|editzero || 13 || 2
|-
|intropreload || 15 || 1
|-
|lastdiff || 14 || 2
|-
|matchsplit || 32 || 1
|-
|ocr || 16 || 3
|-
|popups || 37 || 1
|-
|purgetab || 12 || 2
|-
|yekewarning || 50 || 3
|}
* [[ویژه:استفاده ابزار]]
* [[m:Meta:GUS2Wiki/Script|GUS2Wiki]]
<!-- data in CSV format:
AHK,16,2
BotTools,15,3
CleanDeleteReasons,21,1
CustomizedEditTool,17,3
GoogleTranslator,30,2
GreenRedirect,15,2
HotCat,54,4
ImageAnnotator,35,1
InterwikiTranslate,31,0
UserMessages,15,2
VeExtendedBar,17,2
VeSuperTool,18,2
addon-buttonsformove,36,2
defaultsummaries,20,1
dropdown-menus,19,3
editzero,13,2
intropreload,15,1
lastdiff,14,2
matchsplit,32,1
ocr,16,3
popups,37,1
purgetab,12,2
yekewarning,50,3
-->
8b447ho1dp3gcef6mo6bo9i1w63p4hm
الگو:شعر۲/styles.css
10
97053
305349
305265
2026-10-25T13:38:37Z
Hanooz
17889
305349
sanitized-css
text/css
/*
* الگو:شعر/styles.css — شیوهنامهٔ TemplateStyles برای «پودمان:شعر»
* محتوای این صفحه باید از نوع «Sanitized CSS» باشد؛ پیشوند .mw-parser-output
* را خود افزونه اضافه میکند.
*/
/* ─── فاصلهها (همه یکجا) ──────────────────────────────────────────────────
* فاصلهٔ دو مصراع = ستون میانی (.poem-num-c ≈ ۴em) + حاشیهٔ درونی دو مصراع (۲×۰٫۴em).
* شمارهٔ ابیات به لبهٔ بیرونی میچسبد و فاصلهاش تا مصراع = پهنای ستونِ کناری منهای خودِ شماره.
* در جدولِ هماندازهٔ محتوا (شعر دوطرفه) ستونهای میانی و کناری «انعطافپذیر»اند: وقتی فضا کم
* میشود، پیش از هر چیز همینها کوچک میشوند (بدونِ هیچ media query) و متن مصراعها دستنخورده
* میماند؛ سپس فاصلهٔ واژهها (پایینتر)، و در نهایت چیدمان ستونی.
* (.poem-num کوچکتر (۸۵٪) نوشته میشود، پس emهای آن ×۰٫۸۵ است.)
*/
.poem-m {
padding-left: 0.6em;
padding-right: 0.6em;
}
.poem-num-c {
width: 5.5em;
}
.poem-g {
width: 4.7em;
}
.poem-s {
width: 3.4em;
}
.poem-num-c {
padding-left: 0.5em;
padding-right: 0.5em;
}
.poem-num-r {
width: 4em;
padding-left: 0.5em;
padding-right: 0.5em;
}
.poem-num-l {
width: 4em;
padding-left: 1.4em;
padding-right: 0.5em;
}
/* ─── نستعلیق ───────────────────────────────────────────────────────────────
* قلم و اندازه از خودِ الگوی «نستعلیق» میآید: پودمان {{نستعلیق|…}} را صدا میزند و
* کلاس/قلمی را که آن الگو (و «الگو:نستعلیق/styles.css») میگذارد روی شعر مینشاند؛ پس هر
* تغییر در آن صفحهها خودکار به شعرها میرسد. اینجا فقط میگوییم سلولها و مصراعها از
* شعر ارث ببرند تا قاعدههای سایت برای td/.b قلم را عوض نکنند.
*/
.poem-table.nastaliq td,
.poem-table.nastaliq .beyt {
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
/* مصراعها وسطچین (پایه) */
.poem-row {
text-align: center;
}
/* ─── ترازبندی مصراعها (کلاسهایی که پودمان میگذارد) ─────────────────────────
* start/end بسته به جهت متناند (در فارسی: start = راست، end = چپ).
* «میانه» (پیشفرض بیت): مصراع ۱ end، مصراع ۲ start ← هر دو به محور میانی میچسبند.
* «لبه» (پیشفرض بیت۱ و نردبانی): مصراع ۱ start (راست)، مصراع ۲ end (چپ).
*/
.poem-a-start {
text-align: start;
}
.poem-a-end {
text-align: end;
}
.poem-a-right {
text-align: right;
}
.poem-a-left {
text-align: left;
}
.poem-a-justify {
text-align: justify;
text-align-last: justify;
}
/* ─── شعر دوطرفه ─────────────────────────────────────────────────────────────
* مصراع نمیشکند تا کشش (و عرض همهٔ مصراعها) ثابت بماند؛ ستونهای حاشیه و میان کوچک میشوند.
*/
.poem-just .poem-m {
white-space: nowrap;
}
/* با عرضِ صریح (جدول ثابت) ستونها از پیش تعیین شدهاند: مصراعِ بلندتر بشکند، نه سرریز کند */
.poem-fixed.poem-just .poem-m {
white-space: normal;
}
/* ─── ارجاع (<ref>) در پایان مصراع ───────────────────────────────────────────
* پودمان نشانه را از متن مصراع جدا و در پایانِ آن میگذارد؛ عرضِ صفر یعنی در عرض و کشش مصراع
* نمیشمارد و بیرون از لبهٔ پایانی آویزان میشود. همهٔ مصراعها هماندازه میمانند.
*/
.poem-ref {
display: inline-block;
width: 0;
white-space: nowrap;
}
/* ─── نسخهٔ نامرئیِ اندازهگیر ─────────────────────────────────────────────────────
* در شعر دوطرفه هر سلولِ مصراع یک نسخهٔ نامرئی از مصراعِ همبیت هم دارد (بدون ارجاع)؛ در نتیجه
* هر دو ستون به اندازهٔ بلندترین مصراعِ کل شعر میشوند و همهٔ مصراعها هماندازهاند.
* ارتفاع صفر و نامرئی است، پس چیزی دیده یا انتخاب نمیشود.
*/
.poem-sz {
display: block;
height: 0;
overflow: hidden;
visibility: hidden;
white-space: nowrap;
}
/* نمادِ میان دو مصراع (جداکننده=…) */
.poem-sep {
color: #b3362b;
font-size: 90%;
}
/* شمارهٔ ابیات: کوچکتر، بدون شکستن خط، چسبیده به لبهٔ بیرونی */
.poem-num {
font-size: 85%;
white-space: nowrap;
}
.poem-num-r {
text-align: right;
}
.poem-num-l {
text-align: left;
}
/* مصراع تنها با تراز راست یا چپ (شعر نو) */
.poem-m-right {
text-align: right;
}
.poem-m-left {
text-align: left;
}
/* سلولهای فاصلهگذار در بیتهای پلکانی و ردیف فاصله */
.poem-indent,
.poem-gapcell {
padding: 0;
}
/* ردیف نمادِ میان دو بند (@نماد، {{جداکننده بند}}، جداکننده بند=…): سراسری و وسطچین، به بلندیِ یک خط.
* رنگ از متن ارث میبرد (برای رنگ دیگر: سبک=color:#b3362b)؛ نمادِ میان دو مصراعِ یک بیت (.poem-sep) جداست. */
.poem-ornament .poem-ornament-cell {
padding: 0;
text-align: center;
font-weight: bold;
}
/* ─── چیدمان ثابت (پیشفرض) ──────────────────────────────────────────────────
* ستونها را ردیف نامرئی .poem-sizer تعیین میکند (.poem-s کنار، .poem-g میان)؛
* عرض و حداکثر عرض روی جعبهٔ .poem-fixed مینشیند، پس همهٔ جدولهای یک شعر
* هماندازهاند. (اینجا font-size صفر نمیشود: عرضها با em نوشته شدهاند.)
*/
.poem-fixed .poem-sizer td {
padding: 0;
border: 0;
height: 0;
line-height: 0;
}
.poem-fixed .b {
overflow-wrap: break-word;
}
/* شعر چندصفحهای: ردیف نامرئی که جای «ادامه» مینشیند (خط خالی پدید نیاید) */
.poem-skip {
display: none;
}
/* پیشنمایش در فضای نام «صفحه»: ردیفی که از صفحهٔ قبل میآید یا به صفحهٔ بعد میرود */
.poem-partial-end td.b {
border-bottom: 1px dashed;
}
.poem-partial-start td.b {
border-top: 1px dashed;
}
/* ─── فاصلهٔ واژهها در شعر دوطرفه، پیش از ستونیشدن ───────────────────────────────
* گام ۱ و ۲: فاصلهٔ واژهها اندکی کم میشود تا مصراعها هنوز در یک خط جا شوند.
* (فضای نام «صفحه» آستانههای خودش را دارد؛ بخشِ «فضای نام صفحه» را در پایین ببینید.)
*/
@media (max-width: 70em) {
.poem-just .poem-m {
word-spacing: -0.06em;
}
}
@media (max-width: 60em) {
.poem-just .poem-m {
word-spacing: -0.12em;
}
}
/* ─── صفحهٔ باریک: هر بیت زیر هم، مانند گنجور ────────────────────────────────────
* شماره بالا، دو مصراع وسطچین زیر هم، و خطچینِ جداکننده بعد از هر بیت (در بیتهای پلکانی
* بعد از خط دوم). نماد و نسخهٔ نامرئیِ اندازهگیر حذف میشوند. با تلفن=دوستونه کلاس
* .poem-stack نیست و جدول دوستونه میماند؛ با تلفن=همیشه همیشه ستونی است.
* نکته: سلولِ مصراع flex میشود تا ترازِ دوطرفهٔ درونخطی کل عرض را نکشد؛ انتخابگرهای td.poem-m1/m2
* عمداً از «.poem-row td» ویژهترند وگرنه display:block آنها را بیاثر میکرد.
*/
@media (max-width: 52em) {
.poem-stack .poem-table,
.poem-stack .poem-table tbody,
.poem-stack .poem-row,
.poem-stack .poem-row td {
display: block;
}
.poem-stack .poem-sizer {
display: none;
}
.poem-stack .poem-row td {
width: auto;
text-align: center;
}
.poem-stack .poem-bayt {
display: flex;
flex-direction: column;
align-items: center;
padding: 0.4em 0;
border-bottom: 1px dashed rgba(128, 128, 128, 0.55);
}
.poem-stack .poem-bayt .poem-num-r,
.poem-stack .poem-bayt .poem-num-l {
order: -1;
}
.poem-stack .poem-stagger-1 {
padding-top: 0.4em;
}
.poem-stack .poem-stagger-2 {
padding-bottom: 0.4em;
border-bottom: 1px dashed rgba(128, 128, 128, 0.55);
}
.poem-stack .poem-row td.poem-m1,
.poem-stack .poem-row td.poem-m2 {
display: flex;
justify-content: center;
white-space: normal;
word-spacing: normal;
}
.poem-stack .poem-row td.poem-m1 .beyt,
.poem-stack .poem-row td.poem-m2 .beyt {
margin-left: auto;
margin-right: auto;
text-align: center;
text-align-last: center;
}
.poem-stack .poem-sz,
.poem-stack .poem-sep {
display: none;
}
}
/* ─── فضای نام «صفحه»: فشردهشدن و ستونیشدن بر پایهٔ «طول مصراع» ─────────────────────────
* ستونِ متن در فضای نام «صفحه» (کنار تصویر) حدود نیمی از پنجره است و مرورگر پهنای آن را به شیوهنامه
* نمیدهد (TemplateStyles از container query پشتیبانی نمیکند)؛ پس فقط پهنای پنجره در دسترس است.
* برای اینکه هر شعر نه زودتر از لازم ستونی شود و نه دیرتر، پودمان طولِ بلندترین مصراع را میسنجد و
* یکی از کلاسهای poem-w1 (کوتاه)، poem-w2 (متوسط) یا poem-w3 (بلند) را روی شعر میگذارد
* (پارامتر «طول مصراع=» آن را دستی تعیین میکند)؛ هر سطح آستانهٔ خودش را دارد:
*
* سطح مصراع فشردهشدن (فاصلهها کم میشود) ستونیشدن
* poem-w1 کوتاه تا ۲۰ نویسه ≤ ۸۶em ≤ ۷۶em
* poem-w2 متوسط تا ۳۰ نویسه ≤ ۱۰۶em ≤ ۹۶em
* poem-w3 بلند بیشتر ≤ ۱۲۶em ≤ ۱۱۶em
*
* پایهٔ حساب: هر نویسه ≈ ۰٫۵em؛ جدولِ فشرده ≈ (نویسه + ۸٫۴)em و جدولِ عادی ≈ (نویسه + ۱۳٫۹)em پهنا
* میخواهد؛ ستونِ متن ≈ نیمی از پنجره منهای ۹em فرض شده (em در media query همیشه ۱۶px است).
* اگر قلمِ صفحه درشتتر است همهٔ عددها را بالا ببرید. اگر پنجرهٔ شما عمودی است (تصویر بالای متن)،
* برای شعرِ آن صفحه «تلفن=دوستونه» بگذارید.
* «فشرده»: فاصلهٔ دو مصراع از ≈ ۵٫۹em به ≈ ۳٫۴em میرسد (هنوز فاصلهٔ معقول) و ستونهای شماره باریک
* میشوند؛ مصراعها دستنخورده میمانند. بلوکهای ستونی عیناً همان بلوکهای بالا و پاییناند.
*/
/* poem-w1 (کوتاه): فشرده ≤ 86em، ستونی ≤ 76em */
@media (max-width: 86em) {
.poem-page.poem-w1 .poem-m {
padding-left: 0.3em;
padding-right: 0.3em;
}
.poem-page.poem-w1 .poem-g {
width: 2.8em;
}
.poem-page.poem-w1 .poem-s {
width: 2.2em;
}
.poem-page.poem-w1 .poem-num-c {
width: 2.8em;
}
.poem-page.poem-w1 .poem-num-r,
.poem-page.poem-w1 .poem-num-l {
width: 2.6em;
}
.poem-page.poem-w1.poem-just .poem-m {
word-spacing: -0.06em;
}
}
@media (max-width: 76em) {
.poem-page.poem-stack.poem-w1 .poem-table,
.poem-page.poem-stack.poem-w1 .poem-table tbody,
.poem-page.poem-stack.poem-w1 .poem-row,
.poem-page.poem-stack.poem-w1 .poem-row td {
display: block;
}
.poem-page.poem-stack.poem-w1 .poem-sizer {
display: none;
}
.poem-page.poem-stack.poem-w1 .poem-row td {
width: auto;
text-align: center;
}
.poem-page.poem-stack.poem-w1 .poem-bayt {
display: flex;
flex-direction: column;
align-items: center;
padding: 0.4em 0;
border-bottom: 1px dashed rgba(128, 128, 128, 0.55);
}
.poem-page.poem-stack.poem-w1 .poem-bayt .poem-num-r,
.poem-page.poem-stack.poem-w1 .poem-bayt .poem-num-l {
order: -1;
}
.poem-page.poem-stack.poem-w1 .poem-stagger-1 {
padding-top: 0.4em;
}
.poem-page.poem-stack.poem-w1 .poem-stagger-2 {
padding-bottom: 0.4em;
border-bottom: 1px dashed rgba(128, 128, 128, 0.55);
}
.poem-page.poem-stack.poem-w1 .poem-row td.poem-m1,
.poem-page.poem-stack.poem-w1 .poem-row td.poem-m2 {
display: flex;
justify-content: center;
white-space: normal;
word-spacing: normal;
}
.poem-page.poem-stack.poem-w1 .poem-row td.poem-m1 .beyt,
.poem-page.poem-stack.poem-w1 .poem-row td.poem-m2 .beyt {
margin-left: auto;
margin-right: auto;
text-align: center;
text-align-last: center;
}
.poem-page.poem-stack.poem-w1 .poem-sz,
.poem-page.poem-stack.poem-w1 .poem-sep {
display: none;
}
}
/* poem-w2 (متوسط؛ پیشفرض): فشرده ≤ 106em، ستونی ≤ 96em */
@media (max-width: 106em) {
.poem-page:not(.poem-w1):not(.poem-w3) .poem-m {
padding-left: 0.3em;
padding-right: 0.3em;
}
.poem-page:not(.poem-w1):not(.poem-w3) .poem-g {
width: 2.8em;
}
.poem-page:not(.poem-w1):not(.poem-w3) .poem-s {
width: 2.2em;
}
.poem-page:not(.poem-w1):not(.poem-w3) .poem-num-c {
width: 2.8em;
}
.poem-page:not(.poem-w1):not(.poem-w3) .poem-num-r,
.poem-page:not(.poem-w1):not(.poem-w3) .poem-num-l {
width: 2.6em;
}
.poem-page:not(.poem-w1):not(.poem-w3).poem-just .poem-m {
word-spacing: -0.06em;
}
}
@media (max-width: 96em) {
.poem-page.poem-stack:not(.poem-w1):not(.poem-w3) .poem-table,
.poem-page.poem-stack:not(.poem-w1):not(.poem-w3) .poem-table tbody,
.poem-page.poem-stack:not(.poem-w1):not(.poem-w3) .poem-row,
.poem-page.poem-stack:not(.poem-w1):not(.poem-w3) .poem-row td {
display: block;
}
.poem-page.poem-stack:not(.poem-w1):not(.poem-w3) .poem-sizer {
display: none;
}
.poem-page.poem-stack:not(.poem-w1):not(.poem-w3) .poem-row td {
width: auto;
text-align: center;
}
.poem-page.poem-stack:not(.poem-w1):not(.poem-w3) .poem-bayt {
display: flex;
flex-direction: column;
align-items: center;
padding: 0.4em 0;
border-bottom: 1px dashed rgba(128, 128, 128, 0.55);
}
.poem-page.poem-stack:not(.poem-w1):not(.poem-w3) .poem-bayt .poem-num-r,
.poem-page.poem-stack:not(.poem-w1):not(.poem-w3) .poem-bayt .poem-num-l {
order: -1;
}
.poem-page.poem-stack:not(.poem-w1):not(.poem-w3) .poem-stagger-1 {
padding-top: 0.4em;
}
.poem-page.poem-stack:not(.poem-w1):not(.poem-w3) .poem-stagger-2 {
padding-bottom: 0.4em;
border-bottom: 1px dashed rgba(128, 128, 128, 0.55);
}
.poem-page.poem-stack:not(.poem-w1):not(.poem-w3) .poem-row td.poem-m1,
.poem-page.poem-stack:not(.poem-w1):not(.poem-w3) .poem-row td.poem-m2 {
display: flex;
justify-content: center;
white-space: normal;
word-spacing: normal;
}
.poem-page.poem-stack:not(.poem-w1):not(.poem-w3) .poem-row td.poem-m1 .beyt,
.poem-page.poem-stack:not(.poem-w1):not(.poem-w3) .poem-row td.poem-m2 .beyt {
margin-left: auto;
margin-right: auto;
text-align: center;
text-align-last: center;
}
.poem-page.poem-stack:not(.poem-w1):not(.poem-w3) .poem-sz,
.poem-page.poem-stack:not(.poem-w1):not(.poem-w3) .poem-sep {
display: none;
}
}
/* poem-w3 (بلند): فشرده ≤ 126em، ستونی ≤ 116em */
@media (max-width: 126em) {
.poem-page.poem-w3 .poem-m {
padding-left: 0.3em;
padding-right: 0.3em;
}
.poem-page.poem-w3 .poem-g {
width: 2.8em;
}
.poem-page.poem-w3 .poem-s {
width: 2.2em;
}
.poem-page.poem-w3 .poem-num-c {
width: 2.8em;
}
.poem-page.poem-w3 .poem-num-r,
.poem-page.poem-w3 .poem-num-l {
width: 2.6em;
}
.poem-page.poem-w3.poem-just .poem-m {
word-spacing: -0.06em;
}
}
@media (max-width: 116em) {
.poem-page.poem-stack.poem-w3 .poem-table,
.poem-page.poem-stack.poem-w3 .poem-table tbody,
.poem-page.poem-stack.poem-w3 .poem-row,
.poem-page.poem-stack.poem-w3 .poem-row td {
display: block;
}
.poem-page.poem-stack.poem-w3 .poem-sizer {
display: none;
}
.poem-page.poem-stack.poem-w3 .poem-row td {
width: auto;
text-align: center;
}
.poem-page.poem-stack.poem-w3 .poem-bayt {
display: flex;
flex-direction: column;
align-items: center;
padding: 0.4em 0;
border-bottom: 1px dashed rgba(128, 128, 128, 0.55);
}
.poem-page.poem-stack.poem-w3 .poem-bayt .poem-num-r,
.poem-page.poem-stack.poem-w3 .poem-bayt .poem-num-l {
order: -1;
}
.poem-page.poem-stack.poem-w3 .poem-stagger-1 {
padding-top: 0.4em;
}
.poem-page.poem-stack.poem-w3 .poem-stagger-2 {
padding-bottom: 0.4em;
border-bottom: 1px dashed rgba(128, 128, 128, 0.55);
}
.poem-page.poem-stack.poem-w3 .poem-row td.poem-m1,
.poem-page.poem-stack.poem-w3 .poem-row td.poem-m2 {
display: flex;
justify-content: center;
white-space: normal;
word-spacing: normal;
}
.poem-page.poem-stack.poem-w3 .poem-row td.poem-m1 .beyt,
.poem-page.poem-stack.poem-w3 .poem-row td.poem-m2 .beyt {
margin-left: auto;
margin-right: auto;
text-align: center;
text-align-last: center;
}
.poem-page.poem-stack.poem-w3 .poem-sz,
.poem-page.poem-stack.poem-w3 .poem-sep {
display: none;
}
}
/* همیشه ستونی (تلفن=همیشه) */
.poem-stack-always .poem-table,
.poem-stack-always .poem-table tbody,
.poem-stack-always .poem-row,
.poem-stack-always .poem-row td {
display: block;
}
.poem-stack-always .poem-sizer {
display: none;
}
.poem-stack-always .poem-row td {
width: auto;
text-align: center;
}
.poem-stack-always .poem-bayt {
display: flex;
flex-direction: column;
align-items: center;
padding: 0.4em 0;
border-bottom: 1px dashed rgba(128, 128, 128, 0.55);
}
.poem-stack-always .poem-bayt .poem-num-r,
.poem-stack-always .poem-bayt .poem-num-l {
order: -1;
}
.poem-stack-always .poem-stagger-1 {
padding-top: 0.4em;
}
.poem-stack-always .poem-stagger-2 {
padding-bottom: 0.4em;
border-bottom: 1px dashed rgba(128, 128, 128, 0.55);
}
.poem-stack-always .poem-row td.poem-m1,
.poem-stack-always .poem-row td.poem-m2 {
display: flex;
justify-content: center;
white-space: normal;
word-spacing: normal;
}
.poem-stack-always .poem-row td.poem-m1 .beyt,
.poem-stack-always .poem-row td.poem-m2 .beyt {
margin-left: auto;
margin-right: auto;
text-align: center;
text-align-last: center;
}
.poem-stack-always .poem-sz,
.poem-stack-always .poem-sep {
display: none;
}
/* صفحهٔ باریک با جدولِ دوستونه (تلفن=دوستونه): فاصلهها کمتر تا جا برای متن بماند */
@media (max-width: 30em) {
.poem-m {
padding-left: 0.25em;
padding-right: 0.25em;
}
.poem-num-c {
width: 2.4em;
}
.poem-g {
width: 2em;
}
.poem-s {
width: 2em;
}
.poem-num-r,
.poem-num-l {
width: 2.4em;
}
}
p0a0vt9xwqwqi8sgg1ildc7z48rl6da
پودمان:شعر
828
97054
305350
305264
2026-10-25T13:39:31Z
Hanooz
17889
305350
Scribunto
text/plain
--[==[
پودمان:شعر — نمایش شعر در ویکینبشتهٔ فارسی
الگو نقطهٔ ورود در {{#invoke:شعر|…}}
{{شعر}} open آغاز شعر؛ با پارامتر «متن» کل شعر را میسازد
{{پایان شعر}} close
{{بیت}} bayt بیت دومصراعی
{{بیت۱}} stagger بیت پلکانی (ضربدری)
{{بیت نردبانی}} ladder بیت نردبانی (تورفتگی پیشفرض بیشتر)
{{مصراع}} mesra یک مصراع
{{بیت خالی}} emptyBayt
{{مصراع خالی}} emptyMesra
{{جداکننده بند}} divider نمادِ میان دو بند (• • • یا ❖)، سراسری و وسطچین
ساختار خروجی همان جدول ۷ستونی پیشین است (ظاهر شعرهای موجود و خروجی
کتاب الکترونیکی تغییری نمیکند). شیوهنامه: «الگو:شعر۲/styles.css».
چیدمان (تازه): جدول پیشفرض عرض ثابت و مستقل از محتوا دارد (عرض=، حداکثر عرض=)،
تا همهٔ ابیات شعر — حتی در جدولهای جدا (صفحههای جدا) — از یک نقطه آغاز شوند.
ترازبندی=وسط|راست|چپ|پهن|لبه برای هر نوع بیت؛ قلم=، نستعلیق خودبسنده (بدون Common.css).
در حالت «متن»، خطی که با @ آغاز شود نوع ردیفهای بعدی را عوض میکند: «@بیت نردبانی».
بندها و فاصلهها (تازه):
• «جداکننده بند=• • •» در {{شعر}}: در هر بریدگیِ بند (خط خالی در حالت «متن»، یا {{بیت خالی}})
یک ردیف نمادِ سراسری میآید؛ «جداکننده بند=بله» نمادِ پیشفرض (cfg.stanzaOrnament) را میگذارد.
• حالت «متن»: خطِ «@نماد • • •» فقط همانجا نماد میگذارد («@نماد نه» = فاصلهٔ ساده)؛
خطِ «@فاصله» یک ردیف فاصلهٔ اضافه میآورد («@فاصله 3em» با اندازهٔ دلخواه). این خطها جای
خطهای خالیِ کنارشان را میگیرند و میشود پشتسرهم گذاشت؛ چند خط خالی مثل پیش یک فاصلهاند.
• حالت قالبها: {{جداکننده بند|• • •}} یا {{بیت خالی|نماد=• • •}} (اندازه: ارتفاع=).
• «طول مصراع=کوتاه|متوسط|بلند|شمار نویسه»: در فضای نام «صفحه» آستانهٔ ستونیشدن را تعیین میکند
(حالت «متن»: خودکار سنجیده میشود؛ حالت قالبها: پیشفرض «متوسط»). آستانهها در شیوهنامهاند.
شعر چندصفحهای (ایدهٔ start/end در ppoem):
• در فضای نام «صفحه» هر صفحه همیشه خودبسنده و بسته نمایش داده میشود.
• در صفحهٔ اصلی (درج با <pages/>) پارامتر «ادامه» صفحهها را به هم میدوزد:
{{شعر|ادامه}} جدول تازه باز نمیکند و {{پایان شعر|ادامه}} نمیبندد.
• ردیفِ ناتمام (مصراعی که در صفحهٔ بعد ادامه دارد) نگه داشته میشود
(ناتمام=بله) و با نخستین ردیفِ صفحهٔ بعد (دنباله=بله) یکی میشود.
]==]
local p = {}
---------------------------------------------------------------------------
-- تنظیمات
---------------------------------------------------------------------------
local ZWNJ = '\226\128\140' -- نیمفاصله
local cfg = {
styles = 'Template:شعر۲/styles.css', -- صفحهٔ TemplateStyles
pageNs = 104, -- شمارهٔ فضای نام «صفحه» (ProofreadPage)
mesraUnit = '1.5em', -- تورفتگی هر «:» در حالت متن با نوع=مصراع
nbsp = ' ',
-- چیدمان: 'auto' = جدولِ هماندازهٔ محتوا (رفتار قدیمی)؛ طول/درصد = عرض ثابت
defaultWidth = '100%',
maxWidth = '50em', -- سقف عرض (وقتی عرض داده نشده)
-- فاصلهٔ دو مصراع، ستون شمارهٔ کناری و فاصلهٔ شماره تا مصراع در الگو:شعر۲/styles.css است
-- ترازبندی پیشفرض هر نوع: inner | center | right | left | justify | edge
-- inner (میانه): مصراعها به محور میانی میچسبند — شیوهٔ گنجور و کتابهای کلاسیک
-- edge (لبه) : مصراع ۱ به راست، مصراع ۲ به چپ
justDefault = { bayt = 'inner', stagger = 'edge' },
-- نستعلیق: خودِ الگوی ویکی ({{نستعلیق|…}}) صدا زده میشود و کلاس/قلمی که میگذارد روی شعر
-- مینشیند؛ پس هر تغییر در «الگو:نستعلیق» یا «الگو:نستعلیق/styles.css» خودکار به شعرها میرسد.
nastaliqTemplate = 'نستعلیق',
-- آخرین چاره، فقط اگر آن الگو در دسترس نبود
nastaliqFonts = "'IranNastaliq', 'Nastaliq Navees', 'Noto Nastaliq Urdu', 'Jameel Noori Nastaleeq', 'Nafees Nastaleeq', 'Pak Nastaleeq', serif",
-- نمادِ میان دو مصراع: separator برای همهٔ شعرها ('' = بدون نماد، یا مثلاً '❖')؛
-- ornament نمادِ «جداکننده=بله» است
separator = '',
ornament = '❖',
-- نمادِ پیشفرضِ «جداکنندهٔ بند» (ردیفِ سراسری میان دو بند): @نماد ، {{جداکننده بند}} ، جداکننده بند=بله
stanzaOrnament = '• • •',
-- فضای نام «صفحه»: طولِ بلندترین مصراع (شمار نویسه) ← poem-w1 کوتاه | poem-w2 متوسط | poem-w3 بلند.
-- هر سطح آستانهٔ ستونیشدنِ خودش را در شیوهنامه دارد
hemiShort = 20,
hemiMedium = 30,
-- ترازبندی «پهن»: اگر کوتاهترین مصراعِ بیت کمتر از این نسبتِ طولِ بلندترین باشد، آن بیت
-- کشیده نمیشود (وسطچین میماند) تا فاصلهٔ واژهها بیجهت باز نشود
justifyMinRatio = 0.7,
-- true: ردیف ناتمامِ آخر صفحه نگه داشته و با دنبالهاش ادغام میشود (نیازمند اجرای ترتیبی
-- الگوها در یک رندر). اگر متنی در مرز صفحه گم شد، false کنید: هر تکه فوراً و جدا نمایش داده میشود.
mergeRows = true,
cats = {
unknown = 'صفحه' .. ZWNJ .. 'های دارای پارامتر ناشناخته در الگوهای شعر',
incomplete = 'صفحه' .. ZWNJ .. 'های دارای بیت ناقص',
error = 'صفحه' .. ZWNJ .. 'های دارای خطا در پودمان شعر',
},
}
-- نام پارامترها (فارسی + معادل انگلیسی)
local N = {
right = { 'ش', 'r', 'right' },
center = { 'شو', 'c', 'center' },
left = { 'شچ', 'l', 'left' },
style = { 'سبک', 'style' },
class = { 'کلاس', 'class' },
id = { 'شناسه', 'id' },
indent = { 'تورفتگی', 'indent' },
align = { 'تراز', 'align' },
height = { 'ارتفاع', 'height' },
lang = { 'زبان', 'lang' },
dir = { 'جهت', 'dir' },
text = { 'متن', 'text' },
kind = { 'نوع', 'type' },
numbering = { 'شماره' .. ZWNJ .. 'گذاری', 'شمارهگذاری', 'شماره', 'numbering' },
start = { 'شروع', 'start' },
side = { 'سمت', 'side' },
digits = { 'ارقام', 'digits' },
gap = { 'فاصله', 'gap' },
unit = { 'واحد تورفتگی', 'واحد' .. ZWNJ .. 'تورفتگی', 'واحدتورفتگی', 'indent-unit' },
nastaliq = { 'نستعلیق', 'nastaliq' },
cont = { 'ادامه', 'continue' }, -- شعر از/به صفحهٔ دیگر ادامه دارد
unfinished = { 'ناتمام', 'unfinished' }, -- این ردیف در صفحهٔ بعد ادامه دارد
continued = { 'دنباله', 'continued' }, -- این ردیف ادامهٔ ردیف ناتمامِ صفحهٔ قبل است
joiner = { 'اتصال', 'join' }, -- چسباندن دو تکه: فاصله | چسبیده | نیمفاصله
stanza = { 'بند', 'stanza' }, -- بند=جدید
width = { 'عرض', 'width' },
maxwidth = { 'حداکثر عرض', 'حداکثر' .. ZWNJ .. 'عرض', 'حداکثرعرض', 'max-width' },
sep = { 'جداکننده', 'separator' },
phone = { 'تلفن', 'phone' },
just = { 'ترازبندی', 'تراز' .. ZWNJ .. 'بندی', 'justify' },
ssep = { 'جداکننده بند', 'جداکنندهٔ بند', 'جداکنندۀ بند', 'جداکننده' .. ZWNJ .. 'بند', 'نماد بند', 'stanza-separator' },
symbol = { 'نماد', 'symbol' },
hemilen = { 'طول مصراع', 'طول' .. ZWNJ .. 'مصراع', 'hemistich-length' },
}
local KNOWN = {}
for _, list in pairs(N) do
for _, name in ipairs(list) do
KNOWN[name] = true
end
end
local ALIGN = {
['راست'] = 'right', right = 'right', r = 'right',
['چپ'] = 'left', left = 'left', l = 'left',
['وسط'] = 'center', ['مرکز'] = 'center', center = 'center', c = 'center',
}
local SIDE = { right = 'r', left = 'l', center = 'c' }
local KIND = {
['بیت'] = 'bayt', ['ابیات'] = 'bayt', bayt = 'bayt',
['بیت۱'] = 'stagger', ['بیت1'] = 'stagger', ['پلکانی'] = 'stagger', ['ضربدری'] = 'stagger', stagger = 'stagger',
['بیت نردبانی'] = 'ladder', ['نردبانی'] = 'ladder', ladder = 'ladder',
['مصراع'] = 'mesra', ['نو'] = 'mesra', ['نیمایی'] = 'mesra', ['آزاد'] = 'mesra',
mesra = 'mesra', line = 'mesra',
}
local DIGITS = {
['فارسی'] = 'fa', fa = 'fa', persian = 'fa',
['لاتین'] = 'latin', ['انگلیسی'] = 'latin', latin = 'latin', en = 'latin',
['بدون' .. ZWNJ .. 'تغییر'] = 'keep', ['بدون تغییر'] = 'keep', keep = 'keep',
}
local YES = {
['بله'] = true, ['بلی'] = true, ['آری'] = true, ['خودکار'] = true,
yes = true, y = true, auto = true, ['true'] = true, ['1'] = true,
}
local NO = {
['نه'] = true, ['خیر'] = true, no = true, n = true, ['false'] = true, ['0'] = true,
}
local UNITS = {
[''] = 'px', ['%'] = '%', px = 'px', em = 'em', ex = 'ex', ch = 'ch', rem = 'rem',
vw = 'vw', vh = 'vh', pt = 'pt', cm = 'cm', mm = 'mm', ['in'] = 'in',
}
local CONT = {
['قبل'] = 'prev', ['پیشین'] = 'prev', prev = 'prev',
['بعد'] = 'next', ['پسین'] = 'next', next = 'next',
['هر دو'] = 'both', ['هردو'] = 'both', both = 'both',
}
local JOIN = {
['فاصله'] = ' ', space = ' ',
['چسبیده'] = '', ['بدون فاصله'] = '', tight = '',
['نیم' .. ZWNJ .. 'فاصله'] = ZWNJ, ['نیمفاصله'] = ZWNJ, ['نیم فاصله'] = ZWNJ, zwnj = ZWNJ,
}
local WHICH = {
['هر دو'] = 'both', ['هردو'] = 'both', both = 'both',
['اول'] = 'first', first = 'first', ['دوم'] = 'second', second = 'second',
}
local STANZA = { ['جدید'] = true, new = true }
-- طول مصراع (فضای نام «صفحه»): 1 کوتاه | 2 متوسط | 3 بلند | 0 خودکار
local LEVEL = {
['کوتاه'] = 1, short = 1, ['متوسط'] = 2, medium = 2, ['بلند'] = 3, long = 3,
['خودکار'] = 0, auto = 0,
}
-- خطهای «@…» در حالت «متن» که بریدگیِ بند میسازند: نماد و فاصله
local SYM_WORDS = {
'جداکننده بند', 'جداکنندهٔ بند', 'جداکنندۀ بند', 'جداکننده', 'نماد بند', 'نماد',
'ornament', 'separator', 'symbol',
}
local GAP_WORDS = { 'فاصله', 'gap' }
table.sort(SYM_WORDS, function(a, b) return #a > #b end)
local JUST = {
['وسط'] = 'center', ['مرکز'] = 'center', center = 'center',
['راست'] = 'right', right = 'right',
['چپ'] = 'left', left = 'left',
['پهن'] = 'justify', ['دوطرفه'] = 'justify', ['دو' .. ZWNJ .. 'طرفه'] = 'justify', justify = 'justify',
['لبه'] = 'edge', edge = 'edge',
['میانه'] = 'inner', ['درون'] = 'inner', inner = 'inner',
}
local ALIGN_CLASS = { right = 'poem-a-right', left = 'poem-a-left' }
local JUSTIFY_STYLE = 'text-align:justify;text-align-last:justify'
local PHONE = {
['ستونی'] = 'stack', stack = 'stack',
['دوستونه'] = 'two', ['دو ستونه'] = 'two', ['دو' .. ZWNJ .. 'ستونه'] = 'two', two = 'two',
['همیشه'] = 'always', ['همیشه ستونی'] = 'always', always = 'always',
}
local KIND_KEYS = {}
for k in pairs(KIND) do KIND_KEYS[#KIND_KEYS + 1] = k end
table.sort(KIND_KEYS, function(a, b) return #a > #b end)
local RTL_LANGS = {
ar = true, arz = true, azb = true, bgn = true, ckb = true, dv = true, fa = true, glk = true,
he = true, ks = true, lrc = true, mzn = true, pnb = true, prs = true, ps = true, sd = true,
ug = true, ur = true, yi = true,
}
-- وضعیتِ میان فراخوانیهای یک صفحه (هر تجزیه موتور Lua تازه دارد)
local state = {
ctx = nil, -- شمارهگذاری و ارقام شعرِ باز
open = false, -- آیا شعری باز است؟ (برای دوختن صفحهها در صفحهٔ اصلی)
carry = nil, -- ردیف ناتمام در انتظار دنباله: { kind = , d = }
}
local function defaultCtx()
return { digits = 'fa', fixed = (cfg.defaultWidth ~= 'auto'), sep = cfg.separator }
end
-- ردیف نامرئی: جای خروجی «تهی» تا خط خالی (و در نتیجه <p><br>) پدید نیاید
local SKIP = '<tr class="poem-skip"></tr>'
local CLOSE_HTML = '</table></div>'
---------------------------------------------------------------------------
-- ابزارهای عمومی
---------------------------------------------------------------------------
local function esc(s)
s = s:gsub('"', '"'):gsub('<', '<'):gsub('>', '>')
return s
end
local function pick(args, names)
for _, name in ipairs(names) do
local v = args[name]
if v and v ~= '' then return v end
end
return nil
end
-- آیا یکی از نامها داده شده است (حتی با مقدار تهی)؟
local function present(args, names)
for _, name in ipairs(names) do
if args[name] ~= nil then return true end
end
return false
end
local function bool(v, default)
if v == nil then return default end
v = v:lower()
if YES[v] then return true end
if NO[v] then return false end
return default
end
local function addClass(a, b)
if not a or a == '' then return b end
return a .. ' ' .. b
end
-- مصراع: یکخطیکردن و حذف فاصلهٔ اضافه
local function clean(s)
if not s then return '' end
s = mw.text.trim(s)
s = s:gsub('%s*\n%s*', ' ')
return s
end
-- آرگومانهای الگوی فراخوانکننده (در صورت نبود، آرگومانهای #invoke)
local function getArgs(frame)
local args = {}
local function merge(src)
if not src then return end
for k, v in pairs(src) do
if type(v) == 'string' then args[k] = mw.text.trim(v) end
end
end
merge(frame.args)
local parent = frame:getParent()
if parent then merge(parent.args) end
return args
end
-- آیا پارامتر ناشناخته (یا مصراعِ اضافه) وجود دارد؟
-- اشتباه رایج: وجود «=» در مصراع که آن را به پارامتر نامدار تبدیل میکند.
local function hasUnknown(args, positional)
for k, v in pairs(args) do
if v ~= '' then
if type(k) == 'number' then
if k < 1 or k > positional or k % 1 ~= 0 then return true end
elseif not KNOWN[k] then
return true
end
end
end
return false
end
local function nsId()
return mw.title.getCurrentTitle().namespace
end
-- فضای نام «صفحه»: همیشه خودبسنده نمایش میدهیم
local function inPageNs()
return nsId() == cfg.pageNs
end
-- ردیابی فقط در اسمفضای اصلی و اسمفضاهای محتوایی سفارشی (مثل صفحه)
local function tracked()
local ns = nsId()
return ns == 0 or (ns >= 100 and ns % 2 == 0 and ns ~= 828)
end
local function categories(flags)
if not flags or next(flags) == nil or not tracked() then return '' end
local out = {}
for _, key in ipairs({ 'unknown', 'incomplete', 'error' }) do
if flags[key] then out[#out + 1] = '[[رده:' .. cfg.cats[key] .. ']]' end
end
return table.concat(out)
end
local function langDir(lang)
local base, rest = lang:lower():match('^(%a+)(.*)$')
if not base then return nil end
if rest:find('latn', 1, true) or rest:find('cyrl', 1, true) then return 'ltr' end
if RTL_LANGS[base] or rest:find('arab', 1, true) or rest:find('hebr', 1, true) then return 'rtl' end
return 'ltr'
end
---------------------------------------------------------------------------
-- ارقام
---------------------------------------------------------------------------
local FA = { [0] = '۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹' }
-- mode: 'fa' (پیشفرض) | 'latin' | 'keep'
-- اگر مقدار شبیه نشانهگذاری (HTML/ویکی) باشد دست نمیزنیم.
local function convertDigits(s, mode)
if s == '' or mode == 'keep' then return s end
-- \127 = نشانگر strip (مثل <ref>) که خودش رقم دارد و نباید خراب شود
if s:find('[<>{}&=|\127]') or s:find('%[%[') then return s end
if mode == 'latin' then
s = s:gsub('\217([\160-\169])', function(b) return tostring(b:byte() - 160) end)
s = s:gsub('\219([\176-\185])', function(b) return tostring(b:byte() - 176) end)
return s
end
s = s:gsub('%d', function(d) return FA[tonumber(d)] end)
s = s:gsub('\217([\160-\169])', function(b) return FA[b:byte() - 160] end)
return s
end
local function numberArg(args, names, digits)
local v = pick(args, names)
if not v then return '' end
return convertDigits(v, digits)
end
-- عدد صحیح در بازهٔ [lo, hi] یا nil (از inf و nan و اعداد غولآسا جلوگیری میکند)
local function intArg(v, lo, hi)
local n = tonumber((convertDigits(v, 'latin')))
if not n or n ~= n or n < lo or n > hi then return nil end
return math.floor(n)
end
---------------------------------------------------------------------------
-- اندازههای CSS
---------------------------------------------------------------------------
local function splitLength(v)
if not v or v == '' then return nil end
v = convertDigits(v, 'latin')
v = v:gsub('٪', '%%'):gsub('٫', '.')
local num, unit = v:match('^(%d*%.?%d+)%s*([%a%%]*)$')
if not num then return nil end
num = tonumber(num)
if not num or num > 10000 then return nil end
unit = UNITS[unit:lower()]
if not unit then return nil end
return num, unit
end
local function parseLength(v)
local num, unit = splitLength(v)
if not num then return nil end
return string.format('%g', num) .. unit
end
-- سطح طولِ مصراع بر پایهٔ شمار نویسه: 1 کوتاه | 2 متوسط | 3 بلند
local function levelFor(n)
if n <= cfg.hemiShort then return 1 end
if n <= cfg.hemiMedium then return 2 end
return 3
end
-- نمادِ جداکنندهٔ بند: نه ← '' (بدون نماد) | بله یا تهی ← نمادِ پیشفرض | یک نمادِ کوتاه (• • • ، ❖ …)
local function parseSymbol(v, flags)
if not v or v == '' then return cfg.stanzaOrnament end
local lv = v:lower()
if lv == 'نه' or lv == 'خیر' or lv == 'بدون' or lv == 'ندارد' or lv == 'no' then return '' end
if lv == 'بله' or lv == 'خودکار' or lv == 'yes' or lv == 'auto' then return cfg.stanzaOrnament end
if #v <= 120 and not v:find('[<>{}|%[%]"\n]') then return v end
flags.unknown = true
return cfg.stanzaOrnament
end
---------------------------------------------------------------------------
-- شمارهگذاری خودکار
---------------------------------------------------------------------------
local function newCtx(args, kind, flags)
local ctx = { digits = 'fa', count = 0, start = 1, side = 'l', mesra = (kind == 'mesra') }
local d = pick(args, N.digits)
if d then
ctx.digits = DIGITS[d:lower()]
if not ctx.digits then ctx.digits = 'fa'; flags.unknown = true end
end
local s = pick(args, N.start)
if s then
local n = intArg(s, -1e9, 1e9)
if n then ctx.start = n else flags.unknown = true end
end
local sd = pick(args, N.side)
if sd then
local a = ALIGN[sd:lower()]
if a then ctx.side = SIDE[a] else flags.unknown = true end
end
local m = pick(args, N.numbering)
if m then
local n = intArg(m, 0, 1e6)
if n then
if n >= 1 then ctx.every = n end -- ۰ یعنی خاموش
else
local b = bool(m, nil)
if b == true then ctx.every = 1
elseif b == nil then flags.unknown = true end
end
end
return ctx
end
-- شمارهٔ بعدی (یا nil اگر شمارهگذاری خاموش است یا نوبت نمایش نیست)
local function nextNumber(ctx)
if not ctx.every then return nil end
local n = ctx.start + ctx.count
ctx.count = ctx.count + 1
if n % ctx.every ~= 0 then return nil end
return convertDigits(string.format('%d', n), ctx.digits)
end
-- counted: آیا این ردیف در شمارهگذاری شمرده شود؟ noCenter: ردیف «وسط» ندارد
local function applyAuto(d, ctx, counted, noCenter)
if not counted then return end
local n = nextNumber(ctx)
if not n then return end
if d.r ~= '' or d.c ~= '' or d.l ~= '' then return end -- شمارهٔ دستی مقدم است
local side = ctx.side
if noCenter and side == 'c' then side = 'l' end
d[side] = n
end
---------------------------------------------------------------------------
-- ساخت HTML
---------------------------------------------------------------------------
-- چند تکه استایل را با «;» به هم میچسباند
local function joinStyle(...)
local parts = {}
for i = 1, select('#', ...) do
local v = select(i, ...)
if v and v ~= '' then parts[#parts + 1] = (v:gsub(';%s*$', '')) end
end
return table.concat(parts, ';')
end
-- کلاس ترازِ مصراعِ which (۱ یا ۲) برای ترازبندی j. وسط = پیشفرض شیوهنامه؛ پهن = استایل
-- درونخطی (JUSTIFY_STYLE). ترازها کلاساند تا در صفحهٔ باریک (تلفن) بتوان آنها را وسطچین کرد.
local function alignClass(j, which)
if j == 'justify' then return 'poem-a-justify' end
if not j or j == 'center' then return nil end
if j == 'edge' then return (which == 1) and 'poem-a-start' or 'poem-a-end' end
if j == 'inner' then return (which == 1) and 'poem-a-end' or 'poem-a-start' end
return ALIGN_CLASS[j]
end
local function withClass(base, extra)
if extra then return base .. ' ' .. extra end
return base
end
-- شمار تقریبی نویسههای دیدنیِ یک مصراع (بدون نشانهگذاری و نشانگرهای strip)
local function charLen(s)
s = s:gsub('\127[^\127]*\127', ''):gsub('<[^>]*>', ''):gsub('%[%[[^%]|]*|', ''):gsub('[%[%]]', '')
return #(s:gsub('[\128-\191]', ''))
end
-- «پهن» فقط وقتی که دو مصراع هماندازهاند؛ وگرنه کشش زیاد میشود ← وسطچین
local function effectiveJust(j, a, b)
if j == 'justify' then
local la, lb = charLen(a), charLen(b)
local hi = math.max(la, lb)
if hi > 0 and math.min(la, lb) / hi < cfg.justifyMinRatio then return 'center' end
end
return j
end
local function td(class, colspan, style, content)
local s = '<td class="' .. class .. '"'
if colspan and colspan > 1 then s = s .. ' colspan="' .. colspan .. '"' end
if style and style ~= '' then s = s .. ' style="' .. esc(style) .. '"' end
return s .. '>' .. (content or '') .. '</td>'
end
-- <ref> در پایان مصراع: نشانگر strip (\127'"`UNIQ--ref-…-QINU`"'\127) را جدا میکنیم تا در عرض و
-- کشش مصراع نشمارد؛ نشانه بعد از پایان مصراع میآید (در .poem-ref با عرض صفر آویزان میشود)
local REF_END = '(\127\'"`UNIQ%-%-ref%-%x+%-QINU`"\'\127)%s*$'
local function splitRefs(text)
local refs = ''
while true do
local main, mark = text:match('^(.-)' .. REF_END)
if not mark then break end
refs = mark .. refs
text = main
end
if refs == '' then return text, '' end
return (text:gsub('%s+$', '')), refs
end
-- متنِ ساده (برای نسخهٔ نامرئیِ اندازهگیر): بدون نشانهگذاری، پیوند و نشانگرهای strip
local function plainText(s)
s = s:gsub('\127[^\127]*\127', ''):gsub('<[^>]*>', ''):gsub('%[%[[^%]|]*|', ''):gsub('[%[%]]', ''):gsub("''+", '')
return (s:gsub('%s+', ' '))
end
-- other: مصراعِ همبیت (فقط در شعر دوطرفه). نسخهٔ نامرئیِ آن در همین سلول مینشیند تا عرضِ هر دو ستون
-- برابرِ بلندترین مصراعِ کل شعر شود (همهٔ مصراعها هماندازه؛ هر کدام تا بلندترین کشیده میشود).
local function hemi(text, other)
local main, refs = splitRefs(text)
local h = '<span class="beyt">' .. main
if refs ~= '' then h = h .. '<span class="poem-ref">' .. refs .. '</span>' end
h = h .. '</span>'
if other and other ~= '' then
local t = plainText((splitRefs(other)))
if t ~= '' and t ~= ' ' then h = h .. '<span class="poem-sz">' .. esc(t) .. '</span>' end
end
return h
end
local function rowOpen(d, kind, noId)
local cls = 'poem-row ' .. kind
if d.class then cls = cls .. ' ' .. d.class end
local id = ''
if d.id and not noId then id = ' id="' .. esc(d.id) .. '"' end
return '<tr class="' .. esc(cls) .. '"' .. id .. '>'
end
-- {{بیت}}
local function rowBayt(d)
local m1 = d.m1
if m1 == '' and d.m2 == '' then m1 = cfg.nbsp end -- ردیف خالی ارتفاع خط را حفظ کند
local j = effectiveJust(d.just or cfg.justDefault.bayt, d.m1, d.m2)
local js = (j == 'justify') and JUSTIFY_STYLE or nil
local sz = (j == 'justify')
local mid = d.c
if mid == '' and d.sep and d.sep ~= '' then
mid = '<span class="poem-sep">' .. d.sep .. '</span>' -- نمادِ میان دو مصراع (اگر شمارهٔ میانی نیست)
end
return rowOpen(d, 'poem-bayt')
.. td('poem-num poem-num-r', 1, nil, d.r)
.. td(withClass('b poem-m poem-m1', alignClass(j, 1)), 2, joinStyle(js, d.style), hemi(m1, sz and d.m2 or nil))
.. td('poem-num poem-num-c', 1, d.style, mid)
.. td(withClass('b poem-m poem-m2', alignClass(j, 2)), 2, joinStyle(js, d.style), hemi(d.m2, sz and d.m1 or nil))
.. td('poem-num poem-num-l', 1, nil, d.l)
.. '</tr>'
end
-- پهنای جعبهٔ هر خط در بیت پلکانی با چیدمان ثابت: ۱۰۰٪ منهای تورفتگی
local function boxWidth(indent)
local num, unit = indent:match('^([%d%.]+)(.*)$')
if unit == '%' then
return string.format('%g%%', math.max(100 - tonumber(num), 10))
end
return 'calc(100% - ' .. indent .. ')'
end
-- {{بیت۱}} و {{بیت نردبانی}}
local function rowStagger(d)
local m1 = d.m1
if m1 == '' and d.m2 == '' then m1 = cfg.nbsp end
local j = d.just or cfg.justDefault.stagger -- پلکانی: «پهن» یعنی همهٔ خطها تا بلندترین مصراع (بدون استثنای نسبت)
local js = (j == 'justify') and JUSTIFY_STYLE or nil
local sz = (j == 'justify')
local h1 = sz and d.m2 or nil
local h2 = sz and d.m1 or nil
local s1, s2 = joinStyle(js, d.style), joinStyle(js, d.style)
local c1, c2 = withClass('b poem-m poem-m1', alignClass(j, 1)), withClass('b poem-m poem-m2', alignClass(j, 2))
if not d.indent then
-- پیشفرض: مصراع ۱ در ستونِ راست (ستونهای ۲–۳) و مصراع ۲ در ستونِ چپ (۵–۶) — دقیقاً هماندازهٔ
-- مصراعهای بیت؛ مصراع ۱ راستچین، مصراع ۲ چپچین (ترازبندی=لبه). «تورفتگی» صریح → چیدمان قدیمی.
return rowOpen(d, 'poem-stagger poem-stagger-1')
.. td('poem-num poem-num-r', 1, nil, d.r)
.. td(c1, 2, s1, hemi(m1, h1))
.. td('poem-indent', 3, nil, '')
.. td('poem-num poem-num-l', 1, nil, '')
.. '</tr>\n'
.. rowOpen(d, 'poem-stagger poem-stagger-2', true)
.. td('poem-num poem-num-r', 1, nil, '')
.. td('poem-indent', 3, nil, '')
.. td(c2, 2, s2, hemi(d.m2, h2))
.. td('poem-num poem-num-l', 1, nil, d.l)
.. '</tr>'
end
if d.fixed then
-- چیدمان ثابت: هر خط جعبهای با پهنای درصدی در سلولِ ۵ستونه؛ جعبهٔ اول به آغاز، دوم به پایان میچسبد
local bw = boxWidth(d.indent)
return rowOpen(d, 'poem-stagger poem-stagger-1')
.. td('poem-num poem-num-r', 1, nil, d.r)
.. td(c1, 5, s1,
'<div class="poem-st" style="width:' .. bw .. ';margin-inline-end:auto">' .. hemi(m1, h1) .. '</div>')
.. td('poem-num', 1, nil, '')
.. '</tr>\n'
.. rowOpen(d, 'poem-stagger poem-stagger-2', true)
.. td('poem-num', 1, nil, '')
.. td(c2, 5, s2,
'<div class="poem-st" style="width:' .. bw .. ';margin-inline-start:auto">' .. hemi(d.m2, h2) .. '</div>')
.. td('poem-num poem-num-l', 1, nil, d.l)
.. '</tr>'
end
local sp = 'width:' .. d.indent
return rowOpen(d, 'poem-stagger poem-stagger-1')
.. td('poem-num poem-num-r', 1, nil, d.r)
.. td(c1, 3, s1, hemi(m1, h1))
.. td('poem-indent', 2, sp, '')
.. td('poem-num', 1, nil, '')
.. '</tr>\n'
.. rowOpen(d, 'poem-stagger poem-stagger-2', true)
.. td('poem-num', 1, nil, '')
.. td('poem-indent', 2, sp, '')
.. td(c2, 3, s2, hemi(d.m2, h2))
.. td('poem-num poem-num-l', 1, nil, d.l)
.. '</tr>'
end
-- {{مصراع}}
local function rowMesra(d)
local align = d.align or 'center'
local content = d.text
if d.blank and content == '' then content = cfg.nbsp end
local mainText, refs = splitRefs(content)
if refs ~= '' then content = mainText .. '<span class="poem-ref">' .. refs .. '</span>' end
local style = d.style or ''
local cellClass = 'b poem-m'
local cs = { 2, 3, 2 }
if align ~= 'center' then
cs = { 1, 5, 1 }
cellClass = cellClass .. ' poem-m-' .. align
if d.indent then
style = 'padding-' .. align .. ':' .. d.indent .. ';' .. style
end
end
return rowOpen(d, 'poem-mesra')
.. td('poem-num poem-num-r', cs[1], nil, d.r)
.. td(cellClass, cs[2], style, content)
.. td('poem-num poem-num-l', cs[3], nil, d.l)
.. '</tr>'
end
-- ردیف فاصله با ارتفاع دلخواه (یا یک خط خالی)
local function rowGap(d)
local style = d.height and ('height:' .. d.height) or nil
local content = d.height and '' or cfg.nbsp
return '<tr class="poem-row poem-gap' .. (d.class and (' ' .. esc(d.class)) or '') .. '">'
.. td('poem-gapcell', 7, style, content) .. '</tr>'
end
-- ردیف نمادِ میان دو بند: سراسری و وسطچین (در چیدمان ستونی هم دیده میشود)
local function rowDivider(d)
local style = joinStyle(d.height and ('height:' .. d.height) or nil, d.style)
return '<tr class="poem-row poem-ornament' .. (d.class and (' ' .. esc(d.class)) or '') .. '">'
.. td('poem-ornament-cell', 7, style, d.symbol) .. '</tr>'
end
-- بریدگیِ میان دو بند: فهرستی از { sym = نماد } و { gap = true, height = ارتفاع } به همین ترتیب
local function breakHtml(brk, gapHeight)
local rows = {}
for _, pt in ipairs(brk) do
if pt.sym then
rows[#rows + 1] = rowDivider({ symbol = pt.sym })
else
rows[#rows + 1] = rowGap({ height = pt.height or gapHeight })
end
end
return table.concat(rows, '\n')
end
local RENDER = { bayt = rowBayt, stagger = rowStagger, mesra = rowMesra }
-- ردیف نامرئی نخست در چیدمان ثابت: پهنای ستونها را مستقل از محتوا تعیین میکند
local function sizerRow()
return '<tr class="poem-sizer">'
.. '<td class="poem-s"></td><td></td><td></td>'
.. '<td class="poem-g"></td><td></td><td></td>'
.. '<td class="poem-s"></td></tr>'
end
local function openHtml(o)
local look = o.look -- نمای نستعلیق (کلاس و قلمِ الگوی ویکی)
local cls = 'poem' .. (o.fixed and ' poem-fixed' or '') .. (o.stack and ' poem-stack' or '')
.. (o.just == 'justify' and ' poem-just' or '') .. (o.page and ' poem-page' or '')
.. (o.page and (' poem-w' .. (o.wlevel or 2)) or '')
.. (o.always and ' poem-stack-always' or '')
if look and look.class then cls = cls .. ' ' .. look.class end
if o.class then cls = cls .. ' ' .. o.class end
local div = '<div class="' .. esc(cls) .. '"'
if o.lang then div = div .. ' lang="' .. o.lang .. '"' end
if o.dir then div = div .. ' dir="' .. o.dir .. '"' end
local font = look and look.style or nil
local tcls = 'poem-table' .. (o.nastaliq and ' nastaliq' or '')
if o.fixed then
-- عرض و قلم و سبک کاربر روی جعبهٔ بیرونی؛ جدول همیشه ۱۰۰٪ آن و با ستونهای ثابت
local wrap = joinStyle('width:' .. o.width, 'max-width:' .. o.maxWidth, 'margin:0 auto', font, o.style)
return div .. ' style="' .. esc(wrap) .. '"><table class="' .. tcls
.. '" role="presentation" style="width:100%;table-layout:fixed">' .. sizerRow()
end
-- چیدمان خودکار (قدیمی): همان بایتبهبایتِ پیشین وقتی قلم/سبک نیست
local style = 'margin: 0 auto;'
if font then style = style .. font .. ';' end
if o.style then style = style .. ' ' .. o.style end
return div .. '><table class="' .. tcls .. '" role="presentation" style="' .. esc(style) .. '">'
end
---------------------------------------------------------------------------
-- شعر چندصفحهای: نگهداشتن ردیف ناتمام و یکیکردن آن با دنبالهاش
---------------------------------------------------------------------------
local function joinText(a, b, sep)
if a == '' then return b end
if b == '' then return a end
return a .. sep .. b
end
-- tail را در head (ردیف ناتمام) ادغام میکند: متن مصراعها پشت هم، شمارهها از tail
local function mergeRow(head, tail, sep)
for _, f in ipairs({ 'm1', 'm2', 'text' }) do
if head[f] ~= nil or tail[f] ~= nil then
head[f] = joinText(head[f] or '', tail[f] or '', sep)
end
end
for _, f in ipairs({ 'r', 'c', 'l' }) do
if tail[f] and tail[f] ~= '' then head[f] = tail[f] end
end
head.style = head.style or tail.style
head.class = head.class or tail.class
head.id = head.id or tail.id
return head
end
-- ردیفِ نگهداشتهشده را (اگر باشد) بهصورت HTML بیرون میدهد
local function flushHtml()
local c = state.carry
if not c then return '' end
state.carry = nil
return RENDER[c.kind](c.d)
end
-- ردیفهای بدون منطق ادامه (فاصله، خالی): ردیف ناتمامِ جاافتاده را هم بیرون میریزد
local function plain(html)
if not inPageNs() then
local pre = flushHtml()
if pre ~= '' then return pre .. '\n' .. html end
end
return html
end
--[[
یک ردیف را خروجی میدهد.
فضای نام «صفحه»: همانجا و کامل (فقط کلاسهای ناتمام/دنباله برای نشاندادن).
جاهای دیگر (صفحهٔ اصلی، درج صفحهها): ردیف ناتمام نگه داشته میشود و با
نخستین ردیفِ دارای «دنباله» یکی میشود.
opt: unfinished, continued, joiner
]]
local function emit(kind, d, opt, flags)
opt = opt or {}
if inPageNs() then
if opt.unfinished then d.class = addClass(d.class, 'poem-partial-end') end
if opt.continued then d.class = addClass(d.class, 'poem-partial-start') end
return RENDER[kind](d)
end
if not cfg.mergeRows then return RENDER[kind](d) end -- بدون ادغام: هر تکه فوراً و جدا
local out = ''
local carry = state.carry
state.carry = nil
if carry then
if opt.continued and carry.kind == kind then
d = mergeRow(carry.d, d, opt.joiner or ' ')
else
out = RENDER[carry.kind](carry.d) -- دنبالهای نیامد: همانطور که هست
if opt.continued then flags.unknown = true end -- نوع ردیفها نمیخواند
end
end
if opt.unfinished then
state.carry = { kind = kind, d = d }
return out ~= '' and out or SKIP
end
local html = RENDER[kind](d)
if out ~= '' then return out .. '\n' .. html end
return html
end
---------------------------------------------------------------------------
-- خواندن پارامترها
---------------------------------------------------------------------------
-- ناتمام / دنباله / اتصال
local function rowOpts(args, flags)
local opt = {}
local u = pick(args, N.unfinished)
if u then
local b = bool(u, nil)
if b == nil then flags.unknown = true else opt.unfinished = b end
end
local c = pick(args, N.continued)
if c then
local w = WHICH[c:lower()]
if w then
opt.continued, opt.which = true, w
else
local b = bool(c, nil)
if b == nil then flags.unknown = true else opt.continued = b end
end
end
local j = pick(args, N.joiner)
if j then
opt.joiner = JOIN[j:lower()]
if opt.joiner == nil then flags.unknown = true end
end
return opt
end
-- جداکننده: نه | بله (نماد پیشفرض) | یک نماد کوتاه (مثل ✤)
local function parseSep(v, flags)
local lv = v:lower()
if lv == 'نه' or lv == 'خیر' or lv == 'بدون' or lv == 'ندارد' or lv == 'no' then return '' end
if lv == 'بله' or lv == 'خودکار' or lv == 'yes' or lv == 'auto' then return cfg.ornament end
if #v <= 24 and not v:find('[<>{}|%[%]"\n]') then return v end
flags.unknown = true
return cfg.separator
end
local function parseOpen(args, flags)
local o = {}
local isText = present(args, N.text)
-- پارامترهای بدون نام: نستعلیق، ادامه
for i = 1, 2 do
local v = args[i]
if v and v ~= '' then
local lv = v:lower()
if v == 'نستعلیق' or lv == 'nastaliq' then
o.nastaliq = true
elseif v == 'ادامه' or lv == 'continue' then
if isText then flags.unknown = true else o.cont = 'prev' end
else
flags.unknown = true
end
end
end
if not o.nastaliq then o.nastaliq = bool(pick(args, N.nastaliq), false) end
-- ادامه از/به صفحهٔ دیگر
local c = pick(args, N.cont)
if c then
local k = CONT[c:lower()]
if k then
if not isText and k == 'next' then flags.unknown = true; k = nil end -- «به بعد» را در {{پایان شعر}} بنویسید
else
local b = bool(c, nil)
if b == nil then flags.unknown = true
elseif b then
if isText then flags.unknown = true else k = 'prev' end
end
end
if k then o.cont = k end
end
o.style = pick(args, N.style)
o.class = pick(args, N.class)
local lang = pick(args, N.lang)
if lang then
if lang:match('^%a[%w%-]*$') then o.lang = lang else flags.unknown = true end
end
local dir = pick(args, N.dir)
if dir then
dir = dir:lower()
if dir == 'rtl' or dir == 'ltr' then o.dir = dir else flags.unknown = true end
end
if o.lang and not o.dir then o.dir = langDir(o.lang) end
o.kind = 'bayt'
local k = pick(args, N.kind)
if k then
o.kind = KIND[k:lower()]
if not o.kind then o.kind = 'bayt'; flags.unknown = true end
end
local a = pick(args, N.align)
if a then
o.align = ALIGN[a:lower()]
if not o.align then flags.unknown = true end
end
local g = pick(args, N.gap)
if g then
o.gap = parseLength(g)
if not o.gap then flags.unknown = true end
end
local ind = pick(args, N.indent)
if ind then
o.indent = parseLength(ind)
if not o.indent then flags.unknown = true end
end
o.unitNum, o.unitUnit = splitLength(pick(args, N.unit))
if not o.unitNum then
o.unitNum, o.unitUnit = splitLength(cfg.mesraUnit)
if pick(args, N.unit) then flags.unknown = true end
end
local st = pick(args, N.stanza)
if st then
if STANZA[st:lower()] then o.newStanza = true else flags.unknown = true end
end
-- جداکنندهٔ بند: نماد در هر بریدگیِ بند (خط خالی در حالت «متن»، {{بیت خالی}} در حالت قالبها)
local ss = pick(args, N.ssep)
if ss then o.ssep = parseSymbol(ss, flags) end
-- طول مصراع (آستانهٔ ستونیشدن در فضای نام «صفحه»): کوتاه | متوسط | بلند | شمار نویسه | خودکار
local hl = pick(args, N.hemilen)
if hl then
local lvl = LEVEL[hl:lower()]
if lvl == nil then
local n = intArg(hl, 1, 1000)
if n then lvl = levelFor(n) else flags.unknown = true end
end
if lvl and lvl > 0 then o.wlevel = lvl end
end
-- ترازبندی (همهٔ ابیات)
local jv = pick(args, N.just)
if jv then
o.just = JUST[jv:lower()]
if not o.just then flags.unknown = true end
end
-- جداکننده (نماد میان دو مصراع)
local sv = pick(args, N.sep)
if sv then o.sep = parseSep(sv, flags) else o.sep = cfg.separator end
-- تلفن (صفحهٔ باریک): ستونی (پیشفرض: عدد، دو مصراع زیر هم) یا دوستونه
local stack = true
local ph = pick(args, N.phone)
if ph then
local m = PHONE[ph:lower()]
if m == 'two' then stack = false
elseif m == 'always' then o.always = true
elseif not m then flags.unknown = true end
end
o.stack = stack -- شعر دوطرفه هم در صفحهٔ بسیار باریک ستونی میشود
-- چیدمان: عرض ثابت (پیشفرض) یا «خودکار» = هماندازهٔ محتوا
local w = pick(args, N.width)
if w then
local lw = w:lower()
if lw == 'خودکار' or lw == 'auto' then
o.fixed = false
else
o.width = parseLength(w)
if o.width then o.fixed = true else flags.unknown = true end
end
end
local mx = pick(args, N.maxwidth)
if mx then
local lm = mx:lower()
if lm == 'none' or lm == 'بدون' or lm == 'نامحدود' then
o.maxWidth = 'none'
else
o.maxWidth = parseLength(mx)
if not o.maxWidth then flags.unknown = true end
end
end
if o.fixed == nil then
-- «پهن» (دوطرفه): ستونها به اندازهٔ بلندترین مصراع میشوند تا کمترین کشش لازم باشد؛
-- با دادن «عرض» همان عرضِ ثابت اعمال میشود
o.fixed = (cfg.defaultWidth ~= 'auto') and (o.just ~= 'justify')
end
if o.fixed then
local explicit = (o.width ~= nil)
o.width = o.width or cfg.defaultWidth
o.maxWidth = o.maxWidth or (explicit and '100%' or cfg.maxWidth)
end
return o
end
-- ادامهٔ شعر در صفحهٔ بعد؟ {{پایان شعر|ادامه}} یا ادامه=بعد
local function parseClose(args, flags)
local cont = false
local v = args[1]
if v and v ~= '' then
if v == 'ادامه' or v:lower() == 'continue' then cont = true else flags.unknown = true end
end
local c = pick(args, N.cont)
if c then
local k = CONT[c:lower()]
if k == 'next' or k == 'both' then
cont = true
elseif k == 'prev' then
flags.unknown = true -- «از قبل» را در {{شعر}} بنویسید
else
local b = bool(c, nil)
if b == nil then flags.unknown = true elseif b then cont = true end
end
end
return cont
end
---------------------------------------------------------------------------
-- حالت «متن»: ساخت کل شعر از خطهای ساده
---------------------------------------------------------------------------
--[[
نشانههای درونخطی (به الگوی ppoem):
>> متن مصراع راستچین <> متن مصراع وسطچین
۱۲ <<< متن شمارهٔ سمت راست (ش) متن >>> ۱۲ شمارهٔ سمت چپ (شچ)
:: متن (نوع=مصراع) تورفتگی
]]
local function parseLine(raw, freeVerse)
local t = {}
local s = raw
if freeVerse then
t.level = #s:match('^:*')
s = s:sub(t.level + 1)
end
if s:sub(1, 2) == '<>' then
t.align = 'center'
s = s:sub(3)
elseif s:sub(1, 2) == '>>' and s:sub(3, 3) ~= '>' then
t.align = 'right'
s = s:sub(3)
end
s = mw.text.trim(s)
local pre, rest = s:match('^(.-)%s*<<<%s*(.*)$')
if pre then
t.pre = pre
s = rest
end
local body, post = s:match('^(.-)%s*>>>%s*(.+)$')
if body then
t.post = post
s = body
end
t.text = s
return t
end
-- «@بیت نردبانی تورفتگی=30%» ← { kind = 'ladder', indent = '30%' }؛ پارامتر دوم: گزینهٔ نامعتبر داشت
local function parseDirective(line)
local rest = mw.text.trim(line:sub(2))
local lrest = rest:lower()
for _, key in ipairs(KIND_KEYS) do
if lrest:sub(1, #key) == key then
local after = rest:sub(#key + 1)
if after == '' or after:match('^%s') then
local dd, bad = { kind = KIND[key] }, false
for k, v in after:gmatch('(%S+)=(%S+)') do
local kk, lv = k:lower(), v:lower()
if kk == 'تورفتگی' or kk == 'indent' then
dd.indent = parseLength(v)
if not dd.indent then bad = true end
elseif kk == 'تراز' or kk == 'align' then
dd.align = ALIGN[lv]
if not dd.align then bad = true end
elseif kk == 'ترازبندی' or kk == 'justify' then
dd.just = JUST[lv]
if not dd.just then bad = true end
else
bad = true
end
end
return dd, bad
end
end
end
return nil, true
end
-- «@نماد • • •» ← { sym = '• • •' } | «@نماد نه» ← { gap = true } | «@فاصله 3em» ← { gap = true, height = '3em' }
-- هر خطِ «@…» دیگر (نوعِ ردیف) nil برمیگرداند و parseDirective آن را میخواند
local function parseBreakDirective(line, flags)
local rest = mw.text.trim(line:sub(2))
local lrest = rest:lower()
local function tail(key)
if lrest:sub(1, #key) ~= key then return nil end
local after = rest:sub(#key + 1)
if after ~= '' and not after:match('^[%s=]') then return nil end
return mw.text.trim((after:gsub('^%s*=?', '')))
end
for _, key in ipairs(SYM_WORDS) do
local v = tail(key)
if v then
local s = parseSymbol(v, flags)
if s == '' then return { gap = true } end
return { sym = s }
end
end
for _, key in ipairs(GAP_WORDS) do
local v = tail(key)
if v then
local h
if v ~= '' then
h = parseLength(v)
if not h then flags.unknown = true end
end
return { gap = true, height = h }
end
end
return nil
end
-- طولِ بلندترین مصراعِ متن (شمار نویسه)، برای ردهبندیِ poem-w1/2/3 در فضای نام «صفحه»
local function maxHemiLen(text)
local m = 0
local src = text:gsub('\r\n?', '\n')
for raw in (src .. '\n'):gmatch('([^\n]*)\n') do
local line = mw.text.trim(raw)
if line ~= '' and line:sub(1, 1) ~= '@' then
local n = charLen(clean(parseLine(line, false).text))
if n > m then m = n end
end
end
return m
end
local function renderLines(text, o, ctx, ropt, flags)
-- وضعیت جاری: نوع ردیف و گزینههایش؛ خط «@…» آن را عوض میکند
local cur = { kind = o.kind, indent = o.indent, align = o.align, just = o.just }
local items, pending, pendingCur = {}, nil, nil
-- بریدگیِ بند پیش از ردیفِ بعدی: شمار خطهای خالی (blanks) یا خطهای صریحِ «@نماد/@فاصله» (parts)
local blanks, parts, skipBlanks = 0, {}, false
local function info(c)
local twoH = (c.kind ~= 'mesra')
local rowKind = (c.kind == 'mesra') and 'mesra' or ((c.kind == 'bayt') and 'bayt' or 'stagger')
local indent
if c.kind == 'stagger' or c.kind == 'ladder' then indent = c.indent end -- فقط وقتی صریحاً داده شده
return twoH, rowKind, indent
end
local function numbers(d, t)
if not t then return end
if t.pre and t.pre ~= '' and d.r == '' then d.r = convertDigits(t.pre, ctx.digits) end
if t.post and t.post ~= '' and d.l == '' then d.l = convertDigits(t.post, ctx.digits) end
end
local function makeBayt(t1, t2, c)
local _, _, indent = info(c)
local d = { m1 = t1 and clean(t1.text) or '', m2 = t2 and clean(t2.text) or '',
r = '', c = '', l = '', indent = indent, fixed = ctx.fixed, just = c.just, sep = ctx.sep }
numbers(d, t1)
numbers(d, t2)
return d
end
local function makeMesra(t, c, free)
local d = { text = clean(t.text), r = '', c = '', l = '', fixed = ctx.fixed }
d.align = t.align or c.align or (free and 'right' or 'center')
if free and (t.level or 0) > 0 and d.align ~= 'center' then
d.indent = string.format('%g', o.unitNum * t.level) .. o.unitUnit
end
numbers(d, t)
return d
end
local function push(kind, d, counted)
local brk
if #items > 0 then
if #parts > 0 then
brk = parts
elseif blanks > 0 then
-- خط(های) خالی: فاصلهٔ ساده، یا نمادِ خودکار اگر «جداکننده بند=…» داده شده باشد
brk = { (ctx.ssep and ctx.ssep ~= '') and { sym = ctx.ssep } or { gap = true } }
end
end
parts, blanks, skipBlanks = {}, 0, false
items[#items + 1] = { kind = kind, d = d, counted = counted, brk = brk }
end
local function flushPending()
if pending then
push('mesra', makeMesra(pending, pendingCur, false), false) -- مصراع تنها: وسطچین، بیشمارش
pending = nil
end
end
-- ۱) خط به خط
local src = text:gsub('\r\n?', '\n')
for raw in (src .. '\n'):gmatch('([^\n]*)\n') do
local line = mw.text.trim(raw)
local brkDir = (line:sub(1, 1) == '@') and parseBreakDirective(line, flags) or nil
if line == '' then
flushPending()
if #items > 0 and not skipBlanks then blanks = blanks + 1 end
elseif brkDir then
-- «@نماد …» / «@فاصله …»: بریدگیِ صریح؛ خطهای خالیِ کنارش شمرده نمیشوند
flushPending()
if #items > 0 then
if #parts < 8 then parts[#parts + 1] = brkDir end
blanks, skipBlanks = 0, true
end
elseif line:sub(1, 1) == '@' then
local dd, bad = parseDirective(line)
flushPending()
if dd then
cur = { kind = dd.kind, indent = dd.indent or o.indent, align = dd.align or o.align, just = dd.just or o.just }
end
if bad then flags.unknown = true end
else
skipBlanks = false
local twoH, rowKind = info(cur)
local isFirst = (#items == 0 and not pending)
local t = parseLine(line, not twoH)
if not twoH then
push('mesra', makeMesra(t, cur, true), ctx.mesra) -- مصراعهای نوع «مصراع» فقط در شعرِ نوع=مصراع شمرده میشوند
else
local handled = false
if ropt.continued and isFirst then
-- ادامهٔ ردیفِ ناتمام: کدام مصراع(ها) در این صفحه ادامه دارند؟
local mode = ropt.which or 'auto'
if mode == 'auto' then
local c = state.carry
mode = (c and c.kind ~= 'mesra' and (c.d.m2 or '') == '') and 'second' or 'both'
end
if mode == 'first' then
push(rowKind, makeBayt(t, nil, cur), true); handled = true
elseif mode == 'second' then
push(rowKind, makeBayt(nil, t, cur), true); handled = true
end
end
if not handled then
if pending then
push(rowKind, makeBayt(pending, t, pendingCur), true)
pending = nil
else
pending, pendingCur = t, cur
end
end
end
end
end
if pending then
local _, rowKind = info(pendingCur)
if ropt.unfinished then
push(rowKind, makeBayt(pending, nil, pendingCur), true) -- بیتِ ناتمام: مصراع دوم در صفحهٔ بعد
else
push('mesra', makeMesra(pending, pendingCur, false), false)
end
pending = nil
end
-- ۲) شمارهگذاری (ردیفِ ادامه را دوباره نمیشماریم)
for i, it in ipairs(items) do
if it.counted and not (i == 1 and ropt.continued) then
applyAuto(it.d, ctx, true, it.kind ~= 'bayt')
end
end
-- ۳) خروجی
local out = {}
for i, it in ipairs(items) do
if it.brk then out[#out + 1] = plain(breakHtml(it.brk, o.gap)) end
local ro = {}
if i == 1 and ropt.continued then ro.continued, ro.joiner = true, ropt.joiner end
if i == #items and ropt.unfinished then ro.unfinished = true end
local h = emit(it.kind, it.d, ro, flags)
if h ~= SKIP and h ~= '' then out[#out + 1] = h end
end
return table.concat(out, '\n')
end
---------------------------------------------------------------------------
-- نقطههای ورود
---------------------------------------------------------------------------
-- نمای نستعلیق از روی خودِ الگوی ویکی: {{نستعلیق|x}} را بسط میدهد و از خروجیاش نشانگر
-- شیوهنامه، کلاس و (فقط) استایلهای قلم را برمیدارد. اگر الگو نبود، فهرست قلمِ cfg.
local FONT_PROPS = {
['font-family'] = true, ['font-size'] = true, ['font-style'] = true, ['font-weight'] = true,
['font-variant'] = true, ['font'] = true, ['line-height'] = true, ['letter-spacing'] = true,
['word-spacing'] = true, ['text-rendering'] = true, ['font-feature-settings'] = true,
}
local function nastaliqLook(frame)
local ok, res = pcall(function()
return frame:expandTemplate{ title = cfg.nastaliqTemplate, args = { 'x' } }
end)
if ok and type(res) == 'string' then
local prefix = mw.text.trim(res:match('^(.-)<%a') or '')
local attrs = res:match('<%a+([^>]*)>')
if attrs then
local cls = attrs:match('class%s*=%s*"([^"]*)"') or attrs:match("class%s*=%s*'([^']*)'")
local sty = attrs:match('style%s*=%s*"([^"]*)"') or attrs:match("style%s*=%s*'([^']*)'")
if cls then cls = mw.text.trim((cls:gsub('[^%w%s_%-\128-\255]', ''))) end
local keep = {}
for decl in (sty or ''):gmatch('[^;]+') do
local prop = mw.text.trim(decl:match('^([^:]+):') or ''):lower()
if FONT_PROPS[prop] then keep[#keep + 1] = mw.text.trim(decl) end
end
if (cls and cls ~= '') or #keep > 0 or prefix ~= '' then
return {
prefix = prefix,
class = (cls and cls ~= '') and cls or 'nastaliq',
style = (#keep > 0) and table.concat(keep, ';') or nil,
}
end
end
end
return { prefix = '', class = 'nastaliq', style = 'font-family:' .. cfg.nastaliqFonts }
end
local function open(frame)
local args, flags = getArgs(frame), {}
local o = parseOpen(args, flags)
local isText = present(args, N.text)
local page = inPageNs()
if hasUnknown(args, 2) then flags.unknown = true end
-- صفحهٔ اصلی: اگر شعری باز است و این صفحه ادامهٔ آن است، جدول تازه باز نمیکنیم
if page and not o.wlevel then
-- حالت «متن»: بلندترین مصراع سنجیده میشود؛ حالت قالبها (مصراعها بعداً میرسند): متوسط
o.wlevel = isText and levelFor(maxHemiLen(pick(args, N.text) or '')) or 2
end
o.page = page -- در فضای نام «صفحه» تصویر کنار متن است: فشردهتر و زودتر ستونی
local joins = (not page) and state.open and (o.cont == 'prev' or o.cont == 'both')
local ctx
if joins and state.ctx then
ctx = state.ctx
else
ctx = newCtx(args, o.kind, flags)
ctx.fixed, ctx.just, ctx.sep, ctx.ssep = o.fixed, o.just, o.sep, o.ssep
end
local parts = {}
if not joins then
if o.nastaliq then o.look = nastaliqLook(frame) end
parts[#parts + 1] = frame:extensionTag{ name = 'templatestyles', args = { src = cfg.styles } }
.. ((o.look and o.look.prefix) or '') .. openHtml(o)
state.carry = nil
end
state.open, state.ctx = true, ctx
if isText then
-- کل شعر در یک فراخوانی؛ نیازی به {{پایان شعر}} نیست
local ropt = rowOpts(args, flags)
if o.newStanza and joins and not ropt.continued then
parts[#parts + 1] = plain(breakHtml(
{ (ctx.ssep and ctx.ssep ~= '') and { sym = ctx.ssep } or { gap = true } }, o.gap))
end
local rows = renderLines(pick(args, N.text) or '', o, ctx, ropt, flags)
if rows ~= '' then parts[#parts + 1] = rows end
local stay = (not page) and (o.cont == 'next' or o.cont == 'both')
if not stay then
local tail = flushHtml()
if tail ~= '' then parts[#parts + 1] = tail end
parts[#parts + 1] = CLOSE_HTML
state.open, state.ctx = false, nil
end
end
if #parts == 0 then parts[1] = SKIP end
return table.concat(parts, '\n') .. categories(flags)
end
local function close(frame)
local args, flags = getArgs(frame), {}
local cont = parseClose(args, flags)
if hasUnknown(args, 1) then flags.unknown = true end
if cont and not inPageNs() then
-- شعر در صفحهٔ بعد ادامه دارد: نبندیم (ردیف ناتمام هم نگه داشته میماند)
return SKIP .. categories(flags)
end
local html = flushHtml()
state.open, state.ctx = false, nil
if html ~= '' then html = html .. '\n' end
return html .. CLOSE_HTML .. categories(flags)
end
local function commonRow(args, ctx, flags)
local d = {
r = numberArg(args, N.right, ctx.digits),
c = numberArg(args, N.center, ctx.digits),
l = numberArg(args, N.left, ctx.digits),
style = pick(args, N.style),
class = pick(args, N.class),
id = pick(args, N.id),
fixed = ctx.fixed,
just = ctx.just,
sep = ctx.sep,
}
local sv = pick(args, N.sep)
if sv then d.sep = parseSep(sv, flags) end
local jv = pick(args, N.just)
if jv then
d.just = JUST[jv:lower()]
if not d.just then flags.unknown = true; d.just = ctx.just end
end
return d
end
local function bayt(frame)
local args = getArgs(frame)
local ctx = state.ctx or defaultCtx()
local flags = {}
local opt = rowOpts(args, flags)
local d = commonRow(args, ctx, flags)
d.m1, d.m2 = clean(args[1]), clean(args[2])
if (d.m1 == '') ~= (d.m2 == '') and not opt.unfinished and not opt.continued then
flags.incomplete = true
end
if hasUnknown(args, 2) then flags.unknown = true end
applyAuto(d, ctx, not opt.continued, false)
local html = emit('bayt', d, opt, flags)
return html .. categories(flags)
end
local function staggerFn()
return function(frame)
local args = getArgs(frame)
local ctx = state.ctx or defaultCtx()
local flags = {}
local opt = rowOpts(args, flags)
local d = commonRow(args, ctx, flags)
d.m1, d.m2 = clean(args[1]), clean(args[2])
if (d.m1 == '') ~= (d.m2 == '') and not opt.unfinished and not opt.continued then
flags.incomplete = true
end
local raw = pick(args, N.indent)
d.indent = parseLength(raw)
if raw and not d.indent then flags.unknown = true end
if hasUnknown(args, 2) then flags.unknown = true end
applyAuto(d, ctx, not opt.continued, true)
local html = emit('stagger', d, opt, flags)
return html .. categories(flags)
end
end
local function mesra(frame)
local args = getArgs(frame)
local ctx = state.ctx or defaultCtx()
local flags = {}
local opt = rowOpts(args, flags)
local d = commonRow(args, ctx, flags)
d.text = clean(args[1])
local a = pick(args, N.align)
if a then
d.align = ALIGN[a:lower()]
if not d.align then flags.unknown = true end
end
local raw = pick(args, N.indent)
d.indent = parseLength(raw)
if raw and not d.indent then flags.unknown = true end
if hasUnknown(args, 1) then flags.unknown = true end
applyAuto(d, ctx, ctx.mesra and not opt.continued, true)
local html = emit('mesra', d, opt, flags)
return html .. categories(flags)
end
local function emptyFn(kind)
return function(frame)
local args = getArgs(frame)
local ctx = state.ctx or defaultCtx()
local flags = {}
local d = commonRow(args, ctx, flags)
local raw = pick(args, N.height)
d.height = parseLength(raw)
if raw and not d.height then flags.unknown = true end
if hasUnknown(args, 0) then flags.unknown = true end
-- نماد: «نماد=…» صریح، یا «جداکننده بند=…» در {{شعر}} (فقط وقتی ارتفاع داده نشده)
local sym
local sv = pick(args, N.symbol)
if sv then
sym = parseSymbol(sv, flags)
elseif not d.height and ctx.ssep and ctx.ssep ~= '' then
sym = ctx.ssep
end
local html
if sym and sym ~= '' then
d.symbol = sym
html = rowDivider(d)
elseif d.height then
html = rowGap(d)
elseif kind == 'bayt' then
d.m1, d.m2 = '', ''
html = rowBayt(d)
else
d.text, d.blank = '', true
html = rowMesra(d)
end
return plain(html) .. categories(flags)
end
end
-- {{جداکننده بند}}: ردیف نمادِ سراسری میان دو بند؛ نماد = پارامتر ۱ یا «نماد=» (پیشفرض: cfg.stanzaOrnament)
local function dividerFn(frame)
local args = getArgs(frame)
local ctx = state.ctx or defaultCtx()
local flags = {}
local d = { style = pick(args, N.style), class = pick(args, N.class) }
local sv = pick(args, N.symbol)
if not sv and args[1] and args[1] ~= '' then sv = args[1] end
local sym
if sv then
sym = parseSymbol(sv, flags)
else
sym = (ctx.ssep and ctx.ssep ~= '') and ctx.ssep or cfg.stanzaOrnament
end
local raw = pick(args, N.height)
d.height = parseLength(raw)
if raw and not d.height then flags.unknown = true end
if hasUnknown(args, 1) then flags.unknown = true end
local html
if sym ~= '' then
d.symbol = sym
html = rowDivider(d)
else
html = rowGap(d)
end
return plain(html) .. categories(flags)
end
-- هر خطای پیشبینینشده بهجای شکستن صفحه، پیامی قرمز + رده میدهد
local function safe(fn)
return function(frame)
local ok, res = pcall(fn, frame)
if ok then return res end
return '<span class="error">خطا در پودمان شعر: ' .. esc(tostring(res)) .. '</span>'
.. categories({ error = true })
end
end
p.open = safe(open)
p.close = safe(close)
p.bayt = safe(bayt)
p.stagger = safe(staggerFn())
p.ladder = safe(staggerFn())
p.mesra = safe(mesra)
p.emptyBayt = safe(emptyFn('bayt'))
p.emptyMesra = safe(emptyFn('mesra'))
p.divider = safe(dividerFn)
p.config = cfg -- (برای آزمون و سفارشیسازی)
return p
fc7jbya42b1m0ffslxsu5m7xc0ej9tr
برگه:سرود بهار - مجله سپید و سیاه، شماره ۳۹۳، ۲۶ اسفند ۱۳۳۹، صفحه ۱۶.png
104
98243
305348
305347
2026-10-25T13:36:53Z
Hanooz
17889
305348
proofread-page
text/x-wiki
<noinclude><pagequality level="3" user="Hanooz" /></noinclude>{{وسط بزرگتر۳|سرود بهار}}
{{خطکش|4em}}
{{خطکش|4em}}
{{خطکش|4em}}
{{شعر۲|ترازبندی=پهن|متن=
هوا دلپذیر شد
گل از خاک بردمید
پرستو به بازگشت
بزد نغمهٔ امید
@نماد ⬩ ⬩ ⬩
ز بازی ابر و مهر
به نیلی سپهر ژرف
بهر لحظه تازهای
نمایان شود شگرف
@نماد ⬩ ⬩ ⬩
بجوش آمدست خون
درون رگ گیاه
بهار خجسته فال
خرامان رسد ز راه
@نماد ⬩ ⬩ ⬩
به خویشان و دوستان
به یاران آشنا
به مردان تیزخشم
که پیکار میکنند
به آنانکه با قلم
تباهی دهر را
بچشم جهانیان
پدیدار میکنند
به بانوی سوگوار
که در ماتم شهید
بنالید و زان نوا
دل عالمی تپید
بهاران خجسته باد
و این بند بندگی
و این بار فقر و جهل
بسرتاسر جهان
بهر صورتی که هست
نگون و گسسته باد
}}
{{وسط|اسفند ۳۹ دکتر عبدالله بهزادی}}<noinclude></noinclude>
prxgywfjir5km4rmhhtbc12p65kfuar