quran-madina-html 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Gruntfile.js ADDED
@@ -0,0 +1,52 @@
1
+ module.exports = function(grunt) {
2
+
3
+ grunt.initConfig({
4
+ pkg: grunt.file.readJSON('package.json'),
5
+ concat: {
6
+ options: {
7
+ separator: ';'
8
+ },
9
+ dist: {
10
+ src: ['src/**/*.js', 'demo/**/*.js'],
11
+ dest: 'dist/<%= pkg.name %>.js'
12
+ }
13
+ },
14
+ uglify: {
15
+ options: {
16
+ banner: '/*! <%= pkg.name %> <%= grunt.template.today("dd-mm-yyyy") %> */\n'
17
+ },
18
+ dist: {
19
+ files: {
20
+ 'dist/<%= pkg.name %>-<%= pkg.version %>.min.js': ['<%= concat.dist.dest %>']
21
+ }
22
+ }
23
+ },
24
+ jshint: {
25
+ files: ['Gruntfile.js', 'src/**/*.js', 'test/**/*.js'],
26
+ options: {
27
+ // options here to override JSHint defaults
28
+ globals: {
29
+ jQuery: true,
30
+ console: true,
31
+ module: true,
32
+ document: true
33
+ },
34
+ esnext: true
35
+ }
36
+ },
37
+ watch: {
38
+ files: ['<%= jshint.files %>'],
39
+ tasks: ['jshint']
40
+ }
41
+ });
42
+
43
+ grunt.loadNpmTasks('grunt-contrib-uglify');
44
+ grunt.loadNpmTasks('grunt-contrib-jshint');
45
+ grunt.loadNpmTasks('grunt-contrib-watch');
46
+ grunt.loadNpmTasks('grunt-contrib-concat');
47
+
48
+ grunt.registerTask('test', ['jshint']);
49
+
50
+ grunt.registerTask('default', ['jshint', 'concat', 'uglify']);
51
+
52
+ };
package/README.md ADDED
@@ -0,0 +1,41 @@
1
+ # Quran Madina Html (no-images)
2
+ [![pylint](https://github.com/tarekeldeeb/quran-madina-html-no-images/actions/workflows/pylint.yml/badge.svg)](https://github.com/tarekeldeeb/quran-madina-html-no-images/actions/workflows/pylint.yml)
3
+ [![db_test](https://github.com/tarekeldeeb/quran-madina-html-no-images/actions/workflows/db_test.yml/badge.svg)](https://github.com/tarekeldeeb/quran-madina-html-no-images/actions/workflows/db_test.yml)
4
+ [![npm-grunt](https://github.com/tarekeldeeb/quran-madina-html-no-images/actions/workflows/npm-grunt.yml/badge.svg)](https://github.com/tarekeldeeb/quran-madina-html-no-images/actions/workflows/npm-grunt.yml)
5
+
6
+ A Madina Quran HTML Renderer without images
7
+
8
+ Theres a pre-processing python script to generate Json databases with quran text with special metadata. The preprocessing is based on text from [Tanzil](tanzil.net), and the OCR DB from [Murtaza Raja](https://github.com/murtraja/quran-android-images-helper) helper project.
9
+ Afterwards, the JS library uses those Json objects to render Madina-based Quran pages and lines.
10
+
11
+ The main purpose of this library is:
12
+ * Render Quran text that's visually similar to Madina Printed Pages
13
+ * Efficient Loading of Quran Visual Text (Not image-based, but pure Html)
14
+ * Easy to use: just a simple HTML tag! Example
15
+ ```html
16
+ <quran-madina-html sura="2" aya="8-10"></quran-madina-html>
17
+ ```
18
+
19
+ # Dev Setup
20
+
21
+ ```
22
+ Fork this repo, rename it, then clone it.
23
+
24
+ $ npm install // install bower tasks
25
+ $ bower install // install components
26
+ $ grunt build // build the dependencies
27
+
28
+ ```
29
+ ## Project Status
30
+
31
+ In early stages, no releases yet. Check the demo for PoC.
32
+
33
+ # Demo
34
+
35
+ https://tarekeldeeb.github.io/quran-madina-html-no-images/demo/index.html
36
+
37
+ Don't forget to see the page source!
38
+
39
+ # Links
40
+
41
+ [X-Tags Docs](http://x-tags.org/docs)
Binary file
Binary file
Binary file
package/bower.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "quran-madina-html",
3
+ "description": "",
4
+ "version": "0.1.1",
5
+ "keywords": [
6
+ "web-components",
7
+ "x-tag"
8
+ ],
9
+ "main": [
10
+ "src/main.js",
11
+ "src/main.css"
12
+ ],
13
+ "dependencies": {
14
+ "x-tag-core": "*"
15
+ }
16
+ }
package/build_all.py ADDED
@@ -0,0 +1,30 @@
1
+ """Iterates over build_db.py with multiple configurations
2
+ Fonts covered: Uthman, Amiri Quran Colored
3
+ Font Sizes: Multiple
4
+
5
+ Returns:
6
+ None
7
+ """
8
+ from multiprocessing import Pool
9
+ from argparse import Namespace
10
+ from tqdm import tqdm
11
+ from build_db import run, DEFAULTS, REPO
12
+
13
+ amiri24 = DEFAULTS.copy()
14
+ amiri24['font_size']=24
15
+ amiri24['line_width']=400
16
+
17
+ uthman16 = DEFAULTS.copy()
18
+ uthman16['font_family'] = 'Uthman'
19
+ uthman16['font_url'] = REPO+"assets/fonts/UthmanTN_v2-0.woff2"
20
+
21
+ uthman14 = uthman16.copy()
22
+ uthman14['font_size'] = 14
23
+ uthman14['line_width'] = 240
24
+
25
+ cfgs = [ DEFAULTS, amiri24, uthman16, uthman14 ]
26
+
27
+ if __name__ == '__main__':
28
+ #TODO: Fix the nested progress bar
29
+ with Pool(4) as p:
30
+ r = list(tqdm(p.imap(run, list(map(lambda x: Namespace(**x) ,cfgs))), total=len(cfgs)))
package/build_db.py ADDED
@@ -0,0 +1,269 @@
1
+ """Builds the JSON DB for Quran-Madina-HTML-No-Images project
2
+ It downloads all dependencies, process the Quran String, OCR data
3
+ and finally spits out the JSON DB.
4
+
5
+ Raises:
6
+ Exception: Unexpected Quran Content from DB
7
+
8
+ Returns:
9
+ None
10
+ """
11
+ import os
12
+ import math
13
+ import json
14
+ import re
15
+ import argparse
16
+ import sqlite3
17
+ import requests
18
+ import tqdm
19
+ from bs4 import BeautifulSoup
20
+ from selenium import webdriver
21
+ from selenium.webdriver.chrome.options import Options
22
+
23
+ # Internal Constants: Do not edit!
24
+ TMP = "tmp_download"
25
+ OUT = "DBs"
26
+ DB = "AyahInfo_1024.db"
27
+ TXT = "Uthmani.txt"
28
+ TEST_HTML_TEMPLATE = "part_width_test.html"
29
+ STRETCH_ROUNDING = 3
30
+ REPO = 'https://raw.githubusercontent.com/tarekeldeeb/quran-madina-html-no-images/main/'
31
+ DEFAULTS = {'name':'Madina', 'published': 1985,
32
+ 'title':"مصحف المدينة الإصدار القديم - مجمع الملك فهد لطباعة المصحف الشريف",
33
+ 'font_family':'Amiri Quran Colored',
34
+ 'font_url':REPO+'assets/fonts/AmiriQuranColored.woff2',
35
+ 'font_size':16, 'line_width':260}
36
+
37
+ def _query(query):
38
+ conn = sqlite3.connect(os.path.join(TMP,DB))
39
+ cursor = conn.cursor()
40
+ cursor.execute(query)
41
+ result = cursor.fetchall()
42
+ cursor.close()
43
+ return result
44
+
45
+ def _get_aya_data(sura, ayah):
46
+ result = _query(f'select page_number, line_number from glyphs '
47
+ f'where sura_number={sura} and ayah_number={ayah}')
48
+ return list(map(list, result))
49
+
50
+ def _get_test_filename(font, size):
51
+ suffex = f'-{font}-{size}'
52
+ return "test"+suffex+".html"
53
+
54
+ def _get_surah_name(sura_id):
55
+ sura_name = ["الفاتحة", "البقرة", "آل عمران",
56
+ "النساء", "المائدة", "الأنعام", "الأعراف", "الأنفال", "التوبة",
57
+ "يونس", "هود", "يوسف", "الرعد", "إبراهيم", "الحجر", "النحل",
58
+ "الإسراء", "الكهف", "مريم", "طه", "الأنبياء", "الحج", "المؤمنون",
59
+ "النور", "الفرقان", "الشعراء", "النمل", "القصص", "العنكبوت",
60
+ "الروم", "لقمان", "السجدة", "الأحزاب", "سبأ", "فاطر", "يس",
61
+ "الصافات", "ص", "الزمر", "غافر", "فصلت", "الشورى", "الزخرف",
62
+ "الدخان", "الجاثية", "الأحقاف", "محمد", "الفتح", "الحجرات", "ق",
63
+ "الذاريات", "الطور", "النجم", "القمر", "الرحمن", "الواقعة",
64
+ "الحديد", "المجادلة", "الحشر", "الممتحنة", "الصف", "الجمعة",
65
+ "المنافقون", "التغابن", "الطلاق", "التحريم", "الملك", "القلم",
66
+ "الحاقة", "المعارج", "نوح", "الجن", "المزمل", "المدثر", "القيامة",
67
+ "الإنسان", "المرسلات", "النبأ", "النازعات", "عبس", "التكوير",
68
+ "الانفطار", "المطففين", "الانشقاق", "البروج", "الطارق", "الأعلى",
69
+ "الغاشية", "الفجر", "البلد", "الشمس", "الليل", "الضحى", "الشرح",
70
+ "التين", "العلق", "القدر", "البينة", "الزلزلة", "العاديات",
71
+ "القارعة", "التكاثر", "العصر", "الهمزة", "الفيل", "قريش",
72
+ "الماعون", "الكوثر", "الكافرون", "النصر", "المسد", "الإخلاص",
73
+ "الفلق", "الناس"
74
+ ]
75
+ return "سورة " + sura_name[sura_id]
76
+
77
+ def _make_html(font, font_url, font_sz, line_width):
78
+ with open(TEST_HTML_TEMPLATE, "r", encoding="utf8") as template:
79
+ soup = BeautifulSoup(template.read(), 'html.parser')
80
+ style_elem = soup.find("style").string # type: ignore
81
+ style_elem = style_elem.replace("me_quran", font) # type: ignore
82
+ style_elem = style_elem.replace("Amiri Quran Colored", font)
83
+ style_elem = re.sub(r"https.*woff", font_url, style_elem)
84
+ style_elem = style_elem.replace("260", str(line_width))
85
+ style_elem = style_elem.replace("16", str(font_sz))
86
+ soup.find("style").string = style_elem # type: ignore
87
+ with open(_get_test_filename(font, font_sz), 'w', encoding="utf8") as file:
88
+ file.write(str(soup))
89
+
90
+ def _update_html_text(web_driver, text):
91
+ web_driver.execute_script(f'document.getElementById(\'test\').textContent = \'{text}\'')
92
+
93
+ def _get_width(web_driver):
94
+ return web_driver.execute_script("return document.getElementById('test')"
95
+ ".getBoundingClientRect().width")
96
+
97
+ def _update_line_data(work_pointer, cfg):
98
+ """Calculate the stretching factor (s), then apply to all previous line
99
+ parts and updates their offsets (o).
100
+ Finally, the returns (s,o) for the last part to be written later.
101
+
102
+ Args: work_pointer
103
+ suras (JSON): with complete Ayas
104
+ parts (list): parts of an Aya
105
+ sura (int): Current Surah number
106
+ aya (int): Current Ayah number
107
+ current_line (int): Line Number to update in page
108
+ current_line_width (float): Sum of widths of all line parts
109
+ """
110
+ suras, parts, page, sura, aya, current_line, current_line_width = work_pointer
111
+ look_back = 5
112
+ if current_line_width<20:
113
+ _save_json("", suras, cfg)
114
+ raise ValueError(f'Problem with [short] aya={aya} of sura={sura} at line={current_line}')
115
+ stretch = cfg.line_width/(current_line_width) if page>2 else 1
116
+ aya_look_back = aya-look_back if aya>look_back else 1
117
+ offset = 0
118
+ if aya > 1:
119
+ search_ayas = filter(lambda a: a["p"] == page, suras[sura-1]['ayas'][aya_look_back-1:aya-1])
120
+ for search_aya in search_ayas:
121
+ for search_aya_part in search_aya['r']:
122
+ if search_aya_part['l'] == int(current_line):
123
+ tmp = search_aya_part['o']*stretch
124
+ search_aya_part['o'] = math.ceil(offset)
125
+ offset = offset + tmp
126
+ search_aya_part['s'] = round(stretch, STRETCH_ROUNDING)
127
+ if len(parts) > 0:
128
+ parts[-1]["o"] = math.ceil(offset*stretch)
129
+ parts[-1]["s"] = round(stretch, STRETCH_ROUNDING)
130
+ return suras, parts
131
+
132
+ def _get_json_filename(cfg):
133
+ json_file = f'{cfg.name}-{cfg.font_family.split()[0]}-{cfg.font_size}px.json'
134
+ return os.path.join(OUT, json_file)
135
+
136
+ def _save_json(json_header, suras, cfg):
137
+ j = json.loads(json_header)
138
+ j.update({"suras":suras})
139
+ with open(_get_json_filename(cfg), 'w', encoding="utf-8") as json_file:
140
+ json.dump(j, json_file, ensure_ascii = False, indent=2)
141
+ print(f'Saved: {_get_json_filename(cfg)}')
142
+
143
+ def run(cfg):
144
+ """Runs the build_db module
145
+ """
146
+ try:
147
+ os.mkdir(TMP)
148
+ # Start with downloading the Glyph DB
149
+ url = "https://raw.githubusercontent.com/murtraja/quran-android-images-helper"\
150
+ "/master/static/databases/" + DB.lower()
151
+ response = requests.get(url, timeout=500)
152
+ with open(os.path.join(TMP,DB), "wb") as file_db:
153
+ file_db.write(response.content)
154
+ print("Downloaded Quran DataBase")
155
+ #
156
+ # Then download the text from Tanzil.net
157
+ txt_url = "https://tanzil.net/pub/download/index.php?marks=true&sajdah=true"\
158
+ "&rub=true&tatweel=true&quranType=uthmani&outType=txt-2&agree=true"
159
+ req = requests.get(txt_url, timeout=500, allow_redirects=True)
160
+ text = req.content
161
+ with open(os.path.join(TMP,TXT), "wb") as file_txt:
162
+ file_txt.write(text)
163
+ print("Downloaded Quran Text file.")
164
+ except OSError:
165
+ print("Skipping download ..")
166
+ _make_html(cfg.font_family, cfg.font_url, cfg.font_size, cfg.line_width)
167
+ #
168
+ # Finally Load the Html Template and Driver
169
+ chrome_options = Options()
170
+ chrome_options.add_experimental_option("excludeSwitches", ['enable-automation'])
171
+ web_driver = webdriver.Chrome(options=chrome_options)
172
+ dir_path = os.path.dirname(os.path.realpath(__file__))
173
+ web_driver.get("file://" + os.path.join(dir_path,
174
+ _get_test_filename(cfg.font_family, cfg.font_size)))
175
+ # Lets start building the json output ..
176
+ json_header = f'\
177
+ {{"title": "{cfg.title}",\
178
+ "published": {cfg.published},\
179
+ "font_family": "{cfg.font_family}",\
180
+ "font_url": "{cfg.font_url}",\
181
+ "font_size": {cfg.font_size},\
182
+ "line_width": {cfg.line_width}}}'
183
+ suras = []
184
+ page = aya = current_line_width = current_line = 0
185
+ print("Processing ..")
186
+ with tqdm.tqdm(total=os.path.getsize(os.path.join(TMP,TXT)), leave=False) as progress_bar:
187
+ with open(os.path.join(TMP,TXT), encoding="utf8") as file_txt:
188
+ for aya_line in file_txt:
189
+ progress_bar.update(len(aya_line.encode('utf-8')))
190
+ tokens = aya_line.strip().split('|')
191
+ if len(tokens) == 3:
192
+ prev_aya = aya
193
+ sura, aya, aya_text = tokens
194
+ if "Amiri" in cfg.font_family:
195
+ aya_text = aya_text + f' \u06DD{aya}'
196
+ else:
197
+ aya_text = aya_text.replace("ٱ", "ا") + f' \uFD3F{aya}\uFD3E'
198
+ sura = int(sura)
199
+ aya = int(aya)
200
+ #Add a Sura
201
+ if len(suras)<sura:
202
+ suras.append({"name": _get_surah_name(sura-1), "ayas":[]})
203
+ ayah_data = _get_aya_data(sura, aya)
204
+ if page < ayah_data[0][0]: #new page
205
+ prev_line = current_line
206
+ prev_line_width = current_line_width
207
+ prev_sura = sura-1 if aya==1 else sura
208
+ current_line = 1 if page>1 else 3 if page==1 else 2 #first line in page
209
+ current_line_width = 0
210
+ page = ayah_data[0][0]
211
+ new_page = page>0
212
+ else:
213
+ new_page = False
214
+ lines = {} # Key=LineNumber, Value=Number of glyphs
215
+ parts = [] # Break each ayah into parts/lines
216
+ for glyph in ayah_data:
217
+ if str(glyph[1]) not in lines:
218
+ lines.update({str(glyph[1]):1})
219
+ else:
220
+ lines[str(glyph[1])] = lines[str(glyph[1])] + 1
221
+ if aya==1 and sura!=1 and sura!=9:
222
+ skip_words = 4 #Skip Basmala from first aya. TODO: Add Basmala Manually
223
+ else:
224
+ skip_words = 0
225
+ if len(ayah_data) != len(aya_text.split())-skip_words:
226
+ print(f'Mismatch at {sura}-{aya}: Glyphs={len(ayah_data)} for:{aya_text}')
227
+ for line in lines:
228
+ if line != str(current_line): #new line
229
+ #Override (o,s) of previous line parts
230
+ if new_page:
231
+ work_pointer = (suras, parts, page, prev_sura,
232
+ prev_aya, prev_line, prev_line_width)
233
+ else:
234
+ work_pointer = (suras, parts, page, sura, aya,
235
+ current_line, current_line_width)
236
+ suras, parts = _update_line_data(work_pointer, cfg)
237
+ current_line_width = 0
238
+ current_line = int(line)
239
+ aya_text_part = " ".join(aya_text.split()
240
+ [skip_words:skip_words+lines[line]])
241
+ _update_html_text(web_driver, aya_text_part)
242
+ aya_part_width = _get_width(web_driver)
243
+ current_line_width = current_line_width + aya_part_width
244
+ parts.append({"l":int(line), "t":aya_text_part,
245
+ "o":aya_part_width, "s": 1.0})
246
+ skip_words = skip_words + lines[line]
247
+ suras[sura-1]["ayas"].append({"p":page, "r":parts}) # New completed ayah
248
+ print("Closing Chrome ..")
249
+ web_driver.close()
250
+ os.remove(_get_test_filename(cfg.font_family, cfg.font_size))
251
+ _save_json(json_header, suras, cfg)
252
+
253
+ if __name__ == '__main__':
254
+ parser = argparse.ArgumentParser(description='Build JSON DB for HTML Quran Rendering.')
255
+ parser.add_argument("--name", required=False, default=DEFAULTS["name"],
256
+ help="Mus'haf Short Name")
257
+ parser.add_argument("--title", required=False, default=DEFAULTS["title"],
258
+ help="Mus'haf Long Name")
259
+ parser.add_argument("--published", type=int, required=False, default=DEFAULTS["published"],
260
+ help="Mus'haf Publish Date")
261
+ parser.add_argument("--font_family", required=False, default=DEFAULTS["font_family"],
262
+ help="Font Family")
263
+ parser.add_argument("--font_url", required=False, default=DEFAULTS["font_url"],
264
+ help="Font URL to use")
265
+ parser.add_argument("--font_size", type=int, required=False, default=DEFAULTS["font_size"],
266
+ help="Font Size to render")
267
+ parser.add_argument("--line_width", type=int, required=False, default=DEFAULTS["line_width"],
268
+ help="Page width to render")
269
+ run(parser.parse_args())
package/db_test.py ADDED
@@ -0,0 +1,32 @@
1
+ """Testing the Generated JSON DB including all Quran data
2
+ """
3
+ import unittest
4
+ import glob
5
+ import json
6
+
7
+
8
+ class Test(unittest.TestCase):
9
+ """Testing the Generated JSON DB including all Quran data
10
+ """
11
+ db = []
12
+ for f in glob.glob('./*px.json'):
13
+ with open(f, 'r', encoding="utf-8") as file_handler:
14
+ db.append({"name":f,"data":json.load(file_handler)})
15
+
16
+ def test_0_db_fields(self):
17
+ """Test all JSON header fields if exist
18
+ """
19
+ for field in ['title','published','font_family','font_url',
20
+ 'font_size','line_width','suras']:
21
+ # check if the obtained field is null or not
22
+ for data in self.db:
23
+ self.assertIsNotNone(data["data"][field]) # null will fail the test
24
+
25
+ def test_1_sura_count(self):
26
+ """Test Sura Count
27
+ """
28
+ for data in self.db:
29
+ self.assertEqual(len(data["data"]['suras']), 114, "Missing Suras!")
30
+
31
+ if __name__ == '__main__':
32
+ unittest.main()
@@ -0,0 +1,20 @@
1
+ <!DOCTYPE html>
2
+ <html dir="rtl" lang="ar">
3
+
4
+ <head>
5
+ <meta charset="utf-8" />
6
+ <title>quran-madina-html مشروع</title>
7
+ <script type="text/javascript" src="../dist/quran-madina-html-0.1.2.min.js" data-name="Madina" data-font="Uthman"></script>
8
+ </head>
9
+
10
+ <body>
11
+ <h2>عرض لإمكانية مشروع قران المدينة Html بدون صور</h2>
12
+
13
+ يمكن عرض نص القران قصير بشكله الجميل وسط الكلام مثل قوله تعالى:
14
+ <quran-madina-html sura="1" aya="3"></quran-madina-html>
15
+ واذا تم اختيار ايات تمتد لأكثر من سطر، فيتم عرض عدة أسطر متوافقة مع ترتيب كلمات مصحف المدينة بشكل تلقائي
16
+ <quran-madina-html sura="2" aya="8-10"></quran-madina-html>
17
+ يتميز المشروع بأنه مفتوح المصدر ويسهل تغيير الفونت، كما يعمل على مصاحف غير مصحف المدينة
18
+ </body>
19
+
20
+ </html>