ویکی‌نبشته 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; } /* ─── ارجاع (&lt;ref&gt;) در پایان مصراع ─────────────────────────────────────────── * پودمان نشانه را از متن مصراع جدا و در پایانِ آن می‌گذارد؛ عرضِ صفر یعنی در عرض و کشش مصراع * نمی‌شمارد و بیرون از لبهٔ پایانی آویزان می‌شود. همهٔ مصراع‌ها هم‌اندازه می‌مانند. */ .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 = '&#160;', -- چیدمان: '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('"', '&quot;'):gsub('<', '&lt;'):gsub('>', '&gt;') 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