Opera8 commited on
Commit
9ee8b55
·
verified ·
1 Parent(s): f700e4c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +121 -33
app.py CHANGED
@@ -10,12 +10,13 @@ from docx import Document
10
  from docx.enum.text import WD_ALIGN_PARAGRAPH
11
  from weasyprint import HTML, CSS
12
  import arabic_reshaper
 
13
 
14
- # --- کتابخانه جدید و کلیدی برای تبدیل HTML به DOCX ---
15
  from htmldocx import HtmlToDocx
16
 
17
  app = Flask(__name__)
18
- CORS(app) # --- این خط اضافه شده است تا به همه دامنه‌ها اجازه دسترسی بدهد ---
19
 
20
  # --- ثابت‌ها ---
21
  BASE_DIR = os.path.dirname(os.path.abspath(__file__))
@@ -29,60 +30,140 @@ def get_line_direction(line):
29
  rtl_pattern = re.compile(r'[\u0600-\u06FF\u0750-\u077F]')
30
  return 'rtl' if rtl_pattern.search(line) else 'ltr'
31
 
32
- def reshape_rtl_text(line):
33
- return arabic_reshaper.reshape(line)
 
34
 
35
  # --- توابع ساخت فایل ---
36
 
37
  def get_base_html_for_conversion(text_content):
38
  """
39
- یک رشته HTML پایه تولید می‌کند که هم برای PDF و هم برای DOCX قابل استفاده است.
 
40
  """
41
- content_html_parts = []
42
- for line in text_content.split('\n'):
43
- if not line.strip():
44
- content_html_parts.append('<p> </p>')
45
- continue
46
- direction = get_line_direction(line)
47
- if direction == 'rtl':
48
- reshaped_line = reshape_rtl_text(line)
49
- content_html_parts.append(f'<p style="text-align: right; direction: rtl;">{reshaped_line}</p>')
50
- else:
51
- content_html_parts.append(f'<p style="text-align: left; direction: ltr;">{line}</p>')
52
- return "\n".join(content_html_parts)
 
 
 
 
 
 
 
 
 
 
 
 
53
 
54
 
55
  def create_docx(text_content):
56
  """
57
- DOCX را با تبدیل مستقیم از HTML ایجاد می‌کند.
58
  """
59
  print("--- DOCX Creation: Using HTML to DOCX conversion method ---")
60
  document = Document()
61
  parser = HtmlToDocx()
62
  html_content = get_base_html_for_conversion(text_content)
63
  parser.add_html_to_document(html_content, document)
64
- footer = document.sections[0].footer
65
- footer_p = footer.paragraphs[0] if footer.paragraphs else footer.add_paragraph()
66
- footer_p.text = reshape_rtl_text(FOOTER_TEXT)
 
67
  footer_p.alignment = WD_ALIGN_PARAGRAPH.CENTER
 
 
 
 
68
  buffer = io.BytesIO()
69
  document.save(buffer)
70
  buffer.seek(0)
71
  return buffer
72
 
 
73
  def create_pdf_with_weasyprint(text_content):
74
  """
75
- PDF را از روی یک قالب کامل HTML با فونت سفارشی ایجاد می‌کند.
76
  """
77
  html_body = get_base_html_for_conversion(text_content)
78
  reshaped_footer = reshape_rtl_text(FOOTER_TEXT)
 
79
  full_html = f"""
80
  <!DOCTYPE html><html lang="fa"><head><meta charset="UTF-8"><title>Exported PDF</title>
81
  <style>
82
  @font-face {{ font-family: 'Vazir'; src: url('{FONT_FILE_NAME}'); }}
83
- body {{ font-family: 'Vazir', sans-serif; font-size: 12pt; line-height: 1.8; }}
84
- p {{ margin: 0; padding: 0; }}
85
- .footer {{ position: fixed; bottom: 10px; left: 0; right: 0; text-align: center; color: #007bff; font-size: 10pt; font-family: 'Vazir', sans-serif; }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  </style></head><body>{html_body}
87
  <div class="footer">{reshaped_footer}</div></body></html>
88
  """
@@ -104,9 +185,17 @@ def create_html(text_content):
104
  full_html = f"""
105
  <!DOCTYPE html><html lang="fa"><head><meta charset="UTF-8"><title>Exported File</title>
106
  <style>
107
- body {{ font-size: 12pt; line-height: 1.8; max-width: 800px; margin: 2rem auto; padding: 2rem; border: 1px solid #ddd; font-family: sans-serif; }}
108
- p {{ margin: 0; padding: 0 0 0.5em 0; }}
109
- .footer {{ margin-top: 2rem; padding-top: 1rem; border-top: 1px solid #eee; text-align: center; color: #007bff; font-size: 10pt; }}
 
 
 
 
 
 
 
 
110
  </style></head><body>{html_body}
111
  <div class="footer">{reshaped_footer}</div></body></html>
112
  """
@@ -123,15 +212,14 @@ def process_request(content, file_format):
123
  return send_file(buffer, as_attachment=True, download_name=filename, mimetype=mimetype)
124
 
125
 
126
- # --- ***** تغییر اصلی برای پشتیبانی از Render در اینجا اعمال شده است ***** ---
127
  @app.route('/', methods=['GET', 'POST', 'HEAD'])
128
  def index():
129
- # اگر درخواست از نوع HEAD بود (برای Health Check سرویس Render)
130
- # یک پاسخ موفقیت‌آمیز و خالی برگردان
131
  if request.method == 'HEAD':
132
  return '', 200
133
 
134
- # اگر درخواست از نوع POST بود (کاربر دکمه ساخت فایل را زده)
135
  if request.method == 'POST':
136
  content = request.form.get('content')
137
  file_format = request.form.get('format', 'txt').lower()
@@ -144,4 +232,4 @@ def index():
144
 
145
 
146
  if __name__ == '__main__':
147
- app.run(debug=True)
 
10
  from docx.enum.text import WD_ALIGN_PARAGRAPH
11
  from weasyprint import HTML, CSS
12
  import arabic_reshaper
13
+ import markdown # --- کتابخانه پردازش مارک‌داون برای تشخیص تیترها و بولدها ---
14
 
15
+ # --- کتابخانه کلیدی برای تبدیل HTML به DOCX ---
16
  from htmldocx import HtmlToDocx
17
 
18
  app = Flask(__name__)
19
+ CORS(app) # --- اجازه دسترسی به همه دامنه‌ها ---
20
 
21
  # --- ثابت‌ها ---
22
  BASE_DIR = os.path.dirname(os.path.abspath(__file__))
 
30
  rtl_pattern = re.compile(r'[\u0600-\u06FF\u0750-\u077F]')
31
  return 'rtl' if rtl_pattern.search(line) else 'ltr'
32
 
33
+ def reshape_rtl_text(text):
34
+ # اعمال Reshape برای رفع مشکل جدا نویسی حروف فارسی
35
+ return arabic_reshaper.reshape(text)
36
 
37
  # --- توابع ساخت فایل ---
38
 
39
  def get_base_html_for_conversion(text_content):
40
  """
41
+ یک رشته HTML پایه تولید می‌کند که عناوین، لیست‌ها و متون بولد مارک‌داون
42
+ را به دقت به تگ‌های HTML تبدیل می‌کند و سپس راست‌چین می‌سازد.
43
  """
44
+ # ۱. تبدیل هوشمند متن هوش مصنوعی (مارک‌داون) به HTML خام
45
+ raw_html = markdown.markdown(
46
+ text_content,
47
+ extensions=['extra', 'tables', 'nl2br', 'sane_lists']
48
+ )
49
+
50
+ # ۲. اعمال Reshape روی حروف فارسی (تگ‌های HTML انگلیسی دست‌نخورده می‌مانند)
51
+ reshaped_html = reshape_rtl_text(raw_html)
52
+
53
+ # ۳. تزریق استایل راست‌چین و فونت استاندارد به تمام تگ‌های متنی (هدرها، پاراگراف‌ها، جداول)
54
+ tags_to_rtl =['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ul', 'ol', 'li', 'table', 'th', 'td']
55
+ for tag in tags_to_rtl:
56
+ pattern = re.compile(rf'<{tag}(?P<attrs>\s[^>]*)?>', re.IGNORECASE)
57
+
58
+ def replacer(match):
59
+ attrs = match.group('attrs') or ''
60
+ # اگر تگ از پیش برای کدهای برنامه‌نویسی و فرمول‌ها چپ‌چین تنظیم شده بود به آن دست نمی‌زنیم
61
+ if 'dir="ltr"' in attrs.lower() or 'style="text-align: left' in attrs.lower():
62
+ return match.group(0)
63
+ return f'<{tag}{attrs} style="text-align: right; direction: rtl; font-family: Vazir, sans-serif;">'
64
+
65
+ reshaped_html = pattern.sub(replacer, reshaped_html)
66
+
67
+ return reshaped_html
68
 
69
 
70
  def create_docx(text_content):
71
  """
72
+ DOCX را با تبدیل مستقیم از HTML امل تیترهای بزرگ و متن‌های توپر) ایجاد می‌کند.
73
  """
74
  print("--- DOCX Creation: Using HTML to DOCX conversion method ---")
75
  document = Document()
76
  parser = HtmlToDocx()
77
  html_content = get_base_html_for_conversion(text_content)
78
  parser.add_html_to_document(html_content, document)
79
+
80
+ # قرار دادن متن تبلیغاتی فقط در انتهای فایل (صفحه آخر) به جای پانویس تکراری
81
+ document.add_paragraph("") # ایجاد یک خط فاصله
82
+ footer_p = document.add_paragraph(reshape_rtl_text(FOOTER_TEXT))
83
  footer_p.alignment = WD_ALIGN_PARAGRAPH.CENTER
84
+ # بولد کردن متن تبلیغاتی
85
+ for run in footer_p.runs:
86
+ run.bold = True
87
+
88
  buffer = io.BytesIO()
89
  document.save(buffer)
90
  buffer.seek(0)
91
  return buffer
92
 
93
+
94
  def create_pdf_with_weasyprint(text_content):
95
  """
96
+ PDF را از روی یک قالب کامل HTML با استایل‌های حرفه‌ای (تیتر، کد، فرمول، بولد) ایجاد می‌کند.
97
  """
98
  html_body = get_base_html_for_conversion(text_content)
99
  reshaped_footer = reshape_rtl_text(FOOTER_TEXT)
100
+
101
  full_html = f"""
102
  <!DOCTYPE html><html lang="fa"><head><meta charset="UTF-8"><title>Exported PDF</title>
103
  <style>
104
  @font-face {{ font-family: 'Vazir'; src: url('{FONT_FILE_NAME}'); }}
105
+ body {{
106
+ font-family: 'Vazir', sans-serif;
107
+ font-size: 12pt;
108
+ line-height: 1.8;
109
+ color: #111;
110
+ }}
111
+ /* تنظیمات سایز و استایل عناوین مقالات */
112
+ h1 {{ font-size: 24pt; color: #0d5c75; border-bottom: 2px solid #0d5c75; padding-bottom: 5px; margin-top: 30px; margin-bottom: 15px; }}
113
+ h2 {{ font-size: 20pt; color: #1a7b9c; margin-top: 25px; margin-bottom: 10px; }}
114
+ h3 {{ font-size: 16pt; color: #222; margin-top: 20px; }}
115
+ h4, h5, h6 {{ font-size: 14pt; color: #333; font-weight: bold; }}
116
+ /* تنظیمات متون مهم */
117
+ strong, b {{ font-weight: bold; color: #000; }}
118
+ em, i {{ font-style: italic; color: #444; }}
119
+ /* استایل نقل قول‌ها */
120
+ blockquote {{
121
+ border-right: 4px solid #1095c1;
122
+ margin: 15px 0;
123
+ padding: 10px 15px 10px 0;
124
+ color: #555;
125
+ background-color: #f7fbff;
126
+ border-radius: 4px;
127
+ }}
128
+ /* استایل فرمول‌های ریاضی و کدها */
129
+ code {{
130
+ font-family: monospace;
131
+ background-color: #f1f1f1;
132
+ padding: 2px 6px;
133
+ border-radius: 4px;
134
+ font-size: 11pt;
135
+ direction: ltr;
136
+ display: inline-block;
137
+ color: #c7254e;
138
+ }}
139
+ pre {{
140
+ background-color: #f8f9fa;
141
+ padding: 15px;
142
+ border-radius: 6px;
143
+ border: 1px solid #e1e1e8;
144
+ direction: ltr;
145
+ text-align: left;
146
+ overflow-x: auto;
147
+ }}
148
+ pre code {{ background-color: transparent; padding: 0; color: #333; }}
149
+ /* استایل جداول */
150
+ table {{ border-collapse: collapse; width: 100%; margin-top: 20px; margin-bottom: 20px; }}
151
+ th, td {{ border: 1px solid #ccc; padding: 10px; text-align: right; }}
152
+ th {{ background-color: #e9ecef; font-weight: bold; }}
153
+ p {{ margin: 0 0 12px 0; padding: 0; text-align: justify; }}
154
+
155
+ /* استایل فوتر تغییر یافته: این بخش فقط در انتهای آخرین صفحه و بعد از مقاله قرار می‌گیرد */
156
+ .footer {{
157
+ margin-top: 50px;
158
+ padding-top: 15px;
159
+ border-top: 2px solid #1095c1;
160
+ text-align: center;
161
+ color: #1095c1;
162
+ font-size: 11pt;
163
+ font-weight: bold;
164
+ font-family: 'Vazir', sans-serif;
165
+ page-break-inside: avoid;
166
+ }}
167
  </style></head><body>{html_body}
168
  <div class="footer">{reshaped_footer}</div></body></html>
169
  """
 
185
  full_html = f"""
186
  <!DOCTYPE html><html lang="fa"><head><meta charset="UTF-8"><title>Exported File</title>
187
  <style>
188
+ body {{ font-size: 12pt; line-height: 1.8; max-width: 800px; margin: 2rem auto; padding: 2rem; border: 1px solid #ddd; font-family: sans-serif; direction: rtl; color: #222; }}
189
+ h1 {{ color: #0d5c75; border-bottom: 2px solid #ddd; padding-bottom: 5px; }}
190
+ h2 {{ color: #1a7b9c; }}
191
+ blockquote {{ border-right: 4px solid #1095c1; margin: 0; padding-right: 15px; background: #f9f9f9; padding: 10px; }}
192
+ code {{ font-family: monospace; background-color: #f4f4f4; padding: 2px 5px; border-radius: 4px; direction: ltr; display: inline-block; color: #c7254e; }}
193
+ pre {{ background-color: #f4f4f4; padding: 15px; border-radius: 5px; direction: ltr; text-align: left; overflow-x: auto; }}
194
+ table {{ border-collapse: collapse; width: 100%; }}
195
+ th, td {{ border: 1px solid #ddd; padding: 8px; }}
196
+ th {{ background-color: #f2f2f2; }}
197
+ p {{ margin: 0 0 10px 0; text-align: justify; }}
198
+ .footer {{ margin-top: 3rem; padding-top: 1rem; border-top: 2px solid #1095c1; text-align: center; color: #1095c1; font-weight: bold; font-size: 11pt; }}
199
  </style></head><body>{html_body}
200
  <div class="footer">{reshaped_footer}</div></body></html>
201
  """
 
212
  return send_file(buffer, as_attachment=True, download_name=filename, mimetype=mimetype)
213
 
214
 
215
+ # --- بخش مدیریت روت‌های وب ---
216
  @app.route('/', methods=['GET', 'POST', 'HEAD'])
217
  def index():
218
+ # اگر درخواست از نوع HEAD بود (برای Health Check سرویس) یک پاسخ موفقیت‌آمیز و خالی برگردان
 
219
  if request.method == 'HEAD':
220
  return '', 200
221
 
222
+ # اگر درخواست از نوع POST بود (کاربر دکمه ساخت فایل را زده یا ربات درخواست داده است)
223
  if request.method == 'POST':
224
  content = request.form.get('content')
225
  file_format = request.form.get('format', 'txt').lower()
 
232
 
233
 
234
  if __name__ == '__main__':
235
+ app.run(debug=True)