├── .gitignore
├── LICENSE
├── README.md
├── debug.log
├── dist
├── daf-renderer.esm.js
├── daf-renderer.min.js
└── daf-renderer.umd.js
├── doc-pictures
├── Adding Areas.PNG
├── Area Labels.PNG
├── Comparing Areas.PNG
├── Double-Extend Case.PNG
├── Double-Wrap Case.PNG
├── Spacer Labels.PNG
├── Spacers Together.PNG
├── Spacers.PNG
├── Stairs Case.PNG
└── Three Cases.PNG
├── examples
├── edgeCase.html
├── example1.html
├── example2.html
├── fonts
│ ├── Mekorot-Rashi.ttf
│ ├── Mekorot-Vilna-Bold-Italic.ttf
│ ├── Mekorot-Vilna-Bold.ttf
│ ├── Mekorot-Vilna-Italic.ttf
│ └── Mekorot-Vilna.ttf
└── main.js
├── package-lock.json
├── package-lock.json.1784944010
├── package.json
├── rollup.config.js
└── src
├── calculate-spacers-breaks.js
├── calculate-spacers.js
├── options.js
├── renderer.js
├── style-manager.js
└── styles.css
/.gitignore:
--------------------------------------------------------------------------------
1 | experimental
2 |
3 | node_modules
4 |
5 | .idea
6 |
7 | *.code-workspace
8 | /debug.log
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Dan Jutan Shaun Regenbaum
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 📜 daf-renderer 📜
2 | A DOM render library for creating Talmud pages on the web - *the library works, but documentation and testing are in progress!*
3 |
4 | The goal of this library is to quickly and easily create an HTML object that represents a natively rendered Talmud page, modeled after the classic [Vilna](https://en.wikipedia.org/wiki/Vilna_Edition_Shas#/media/File:First_page_of_the_first_tractate_of_the_Talmud_(Daf_Beis_of_Maseches_Brachos).jpg). It can also work for any other purposes that require a similar layout. Our library has **no dependencies** and can be used with **two lines of code**.
5 |
6 | ## Installation
7 |
8 | ### Release File
9 |
10 | Download our [release](https://github.com/GT-Jewish-DH/daf-renderer/releases/tag/v0.1) and include the single .js file in your project.
11 |
12 | ### NPM
13 |
14 | If you're using a [module bundler](https://medium.com/@gimenete/how-javascript-bundlers-work-1fc0d0caf2da) for your front-end project, you can install via npm:
15 |
16 | `npm install daf-renderer`
17 |
18 | Then in your code
19 |
20 | `import dafRenderer from "daf-renderer"`
21 |
22 | ## Usage
23 |
24 |
25 | ```javascript
26 | const renderer = dafRenderer("#dafGoesHere", options);
27 | renderer.render(mainHTML, innerHTML, outerHTML, "b");
28 | ```
29 | For examples on how to use this, refer to the example files provided.
30 |
31 | *Note: if you're loading fonts to be used in the input texts, make sure these fonts are fully loaded before calling the render method.*
32 |
33 | ### Options (optional)
34 | The renderer can take into account a number of customizable options. If these options are not included, then it will default to what you find below. If you want to change anything, you simply need to pass in an object to the dafRenderer as outlined below with your desired changes.
35 | There are seven options you can change:
36 | 1. *contentWidth*: This option controls how wide the page should be. Everything else will be automatically adapted to fit the content within the designated width.
37 | 2. *mainWidth*: This option controls the percentage of the *contentWidth* which the main body of text takes up. For example, if you set this to 50%, then the main body will take up .5 x *contentWidth*, leaving 25% of the width to each of the side bodies of text.
38 | 3. *padding*: This determines the padding within the rendered object, not outside of it, specifically the padding between the three different bodies of text. Anywhere where two bodies of text are next to one another, this option will control how far apart the two texts are. If there is vertical space, the vertical padding will control how large that space is, and if there is horinzontal space, the horizontal padding will control how large that space is.
39 | 4. *fontFamily*: This option controls the fonts that the renderer uses. You can use any of the standard web fonts, or Rashi and Vilna (which are included). If you would like to use other fonts, simply make sure to include them in your file strucutre and refer to them here.
40 | 5. *direction*: This controls the direction of the text. For English use "ltr", otherwise it will default to "rtl".
41 | 6. *fontSize*: This controls the font size of the different bodies of text. For now, you cannot set the inner and outer font sizes or line heights independently; both the inner and outer side texts use the size specified under `side`.
42 | 7. *lineHeight*: This option controls the vertical spacing between lines within a single body of text. Be careful with making this value too small as it may introduce rendering problems.
43 |
44 | ```javascript
45 | {
46 | contentWidth: "600px",
47 | mainWidth: "50%",
48 | padding: {
49 | vertical: "10px",
50 | horizontal: "16px",
51 | },
52 | fontFamily: {
53 | inner: "Rashi",
54 | outer: "Rashi",
55 | main: "Vilna"
56 | },
57 | direction: "rtl",
58 | fontSize: {
59 | main: "15px",
60 | side: "10.5px"
61 | },
62 | lineHeight: {
63 | main: "17px",
64 | side: "14px",
65 | },
66 | }
67 | ```
68 |
69 | ### Data Sources
70 |
71 | #### Sefaria
72 | *Coming Soonish...*
73 |
74 | #### Talmud.dev API
75 | *Coming Soonish...*
76 |
77 | ## How it Works (Better Documenation On the Way)
78 |
79 | ### Spacers and the DOM
80 | The layout of the Talmud is not easily replicated with the box-model of the web. This is because there is no such thing as *middle* for the CSS [float](https://developer.mozilla.org/en-US/docs/Web/CSS/float) property, or any other kind of ability to allow multiple bodies of text to wrap around one another. This limitation was overcome with a new paradigm we call *spacers*. Spacers take advantage of the wrap-around principles of [flow](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Normal_Flow). When a *right-floated* body of text encounters a *left-floated* element, it will wrap around that element instead of overlapping. Thus, we can make complex shapes out of the text by using multiple spacers to force the text into the shape we want:
81 |
82 | 
83 |
84 | If we use three layers stacked on top of each other, we can then recreate the page in its entirety:
85 |
86 | 
87 |
88 | We can see above that there are many spacers, but we only need to worry about four of them. We will define them as such:
89 | - Start Spacer
90 | - Inner Spacer
91 | - Outer Spacer
92 | - Bottom Spacer
93 |
94 | Once we have this structure, where there are three layers each with their own spacers, the only thing left is to calculate the dimensions of the spacers listed above. Specifically, it is important to know their heights.
95 |
96 |
97 | ### Algorithm
98 | The algorithim has two stages.
99 |
100 | In order to understand both stages, we must understand that there are three possible layouts:
101 |
102 | - Double-Wrap
103 | - Stairs
104 | - Double-Extend
105 |
106 | 
107 |
108 |
109 |
110 | The first stage of the algorithim determines which layout the current page is. In order to do this there are three-five steps:
111 |
112 | 1. First we calculate the area that each body of text occupies (in terms of px^2).
113 | 2. Second we divide the calculated area by each text's respective width to get an expected height.
114 |
115 | 
116 |
117 | 3. Third we compare these expected heights. If the main text has the smallest height then we know that we are dealing with the case of **Double-Wrap**.
118 |
119 | 
120 |
121 | 4. If the main text is not smallest we then add the areas of the two smallest texts and divide that by their added widths to get a new height.
122 |
123 | 
124 |
125 | 5. We then compare the new height to the largest expected height. If the new height is smaller than the largest expected height then we are dealing with the case of **Stairs**. If not, we are dealing with the case of **Double-Extend**.
126 |
127 | The second stage of the algorithim calculates the spacer heights based on the type of layout the page is.
128 | This stage requires only three things:
129 | - The type of layout
130 | - The calculated area values from before
131 | - The padding values that we can optionally apply
132 |
133 | We will divide the respective calculations into three parts corresponding to the three kinds of layouts:
134 |
135 |
136 | For the case of Double-Wrap:
137 | - Inner Spacer = Main Area / Main Width
138 | - Outer Spacer = Main Area / Main Width
139 | - End Spacer = (Inner or Outer Area - (Main Area / Main Width) * Side Width) / Top Width
140 |
141 |
142 | For the case of Stairs:
143 | - Inner or Outer Spacer = Stair Area / Side Width
144 | - Outer or Inner Spacer = (Main Area + Stair Area - Padding Area) / Combined Width
145 | - Padding Area = (Combined Height - Stair Height) * Horizontal Padding
146 | - End Spacer = 0
147 |
148 |
149 | For the case of Double Extend:
150 | - Inner Spacer = Inner Area / Side Width
151 | - Outer Spacer = Outer Area / Side Width
152 | - End Spacer = 0
153 |
154 |
155 |
156 |
157 | ## License
158 |
159 | MIT License
160 |
161 | Copyright (c) 2020 Dan Jutan Shaun Regenbaum
162 |
163 | Permission is hereby granted, free of charge, to any person obtaining a copy
164 | of this software and associated documentation files (the "Software"), to deal
165 | in the Software without restriction, including without limitation the rights
166 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
167 | copies of the Software, and to permit persons to whom the Software is
168 | furnished to do so, subject to the following conditions:
169 |
170 | The above copyright notice and this permission notice shall be included in all
171 | copies or substantial portions of the Software.
172 |
173 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
174 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
175 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
176 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
177 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
178 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
179 | SOFTWARE.
180 |
181 | ## Development
182 | Local setup, contributions, issues guidelines etc.
183 |
184 |
185 |
--------------------------------------------------------------------------------
/debug.log:
--------------------------------------------------------------------------------
1 | [0119/094405.534:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3)
2 |
--------------------------------------------------------------------------------
/dist/daf-renderer.esm.js:
--------------------------------------------------------------------------------
1 | const defaultOptions = {
2 | contentWidth: "600px",
3 | mainWidth: "50%",
4 | padding: {
5 | vertical: "10px",
6 | horizontal: "16px",
7 | },
8 | innerPadding: "4px",
9 | outerPadding: "4px",
10 | halfway: "50%",
11 | fontFamily: {
12 | inner: "Rashi",
13 | outer: "Rashi",
14 | main: "Vilna"
15 | },
16 | direction: "rtl",
17 | fontSize: {
18 | main: "15px",
19 | side: "10.5px"
20 | },
21 | lineHeight: {
22 | main: "17px",
23 | side: "14px",
24 | }
25 | };
26 |
27 | function mergeAndClone (modified, definitional = defaultOptions) {
28 | const newOptions = {};
29 | for (const key in definitional) {
30 | if (key in modified) {
31 | const defType = typeof definitional[key];
32 | if (typeof modified[key] !== defType) {
33 | console.error(`Option ${key} must be of type ${defType}; ${typeof modified[key]} was passed.`);
34 | }
35 | if (defType == "object") {
36 | newOptions[key] = mergeAndClone(modified[key], definitional[key]);
37 | } else {
38 | newOptions[key] = modified[key];
39 | }
40 | } else {
41 | newOptions[key] = definitional[key];
42 | }
43 | }
44 | return newOptions;
45 | }
46 |
47 | function getAreaOfText(text, font, fs, width, lh, dummy) {
48 | let testDiv = document.createElement("div");
49 | testDiv.style.font = String(fs) + "px " + String(font);
50 | testDiv.style.width = String(width) + "px"; //You can remove this, but it may introduce unforseen problems
51 | testDiv.style.lineHeight = String(lh) + "px";
52 | testDiv.innerHTML = text;
53 | dummy.append(testDiv);
54 | let test_area = Number(testDiv.clientHeight * testDiv.clientWidth);
55 | testDiv.remove();
56 | return test_area;
57 | }
58 |
59 | function calculateSpacers(mainText, innerText, outerText, options, dummy) {
60 |
61 | const parsedOptions = {
62 | width: parseFloat(options.contentWidth),
63 | padding: {
64 | vertical: parseFloat(options.padding.vertical),
65 | horizontal: parseFloat(options.padding.horizontal)
66 | },
67 | halfway: 0.01 * parseFloat(options.halfway),
68 | fontFamily: options.fontFamily,
69 | fontSize: {
70 | main: parseFloat(options.fontSize.main),
71 | side: parseFloat(options.fontSize.side),
72 | },
73 | lineHeight: {
74 | main: parseFloat(options.lineHeight.main),
75 | side: parseFloat(options.lineHeight.side),
76 | },
77 | mainWidth: 0.01 * parseFloat(options.mainWidth)
78 | };
79 |
80 | const midWidth = Number(parsedOptions.width * parsedOptions.mainWidth) - 2*parsedOptions.padding.horizontal; //main middle strip
81 | const topWidth = Number(parsedOptions.width * parsedOptions.halfway) - parsedOptions.padding.horizontal; //each commentary top
82 | const sideWidth = Number(parsedOptions.width * (1 - parsedOptions.mainWidth)/2); //each commentary widths, dont include padding, sokeep it constant
83 |
84 | const spacerHeights = {
85 | start: 4.3 * parsedOptions.lineHeight.side,
86 | inner: null,
87 | outer: null,
88 | end: 0,
89 | exception: 0
90 | };
91 |
92 | // We are accounting for the special case, where you have line breaks:
93 | // if (options.lineBreaks) {
94 | // console.log("Special Case for Line Breaks")
95 | // const main = {
96 | // name: "main",
97 | // text: mainText,
98 | // lineHeight: parsedOptions.lineHeight.main,
99 | // top: 0,
100 | // }
101 | // const outer = {
102 | // name: "outer",
103 | // text: outerText,
104 | // lineHeight: parsedOptions.lineHeight.side,
105 | // top: 4,
106 | // }
107 | // const inner = {
108 | // name: "inner",
109 | // text: innerText,
110 | // lineHeight: parsedOptions.lineHeight.side,
111 | // top: 4,
112 | // }
113 | //
114 | // const texts = [main, outer, inner];
115 | // texts.forEach(body => body.brCount = (body.text.match(/
/g) || []).length - body.top);
116 | // texts.forEach(body => body.height = (body.brCount * body.lineHeight));
117 | // texts.forEach(body => body.unadjustedHeight = ((body.brCount + body.top + 1) * body.lineHeight));
118 | // texts.forEach(body => body.unadjustedHeightAlt = ((body.brCount + body.top) * body.lineHeight)*sideWidth/topWidth);
119 | // const perHeight = Array.from(texts).sort((a, b) => a.height - b.height);
120 | //
121 | // const exConst = 2.2
122 | //
123 | // //Checking Exceptions:
124 | // if (inner.unadjustedHeight <= 0 && outer.unadjustedHeight <= 0){
125 | // console.error("No Commentary");
126 | // return Error("No Commentary");
127 | // };
128 | // if (inner.unadjustedHeightAlt/exConst < spacerHeights.start || outer.unadjustedHeightAlt/exConst < spacerHeights.start) {
129 | // console.log("Exceptions")
130 | // if (inner.unadjustedHeightAlt/exConst <= spacerHeights.start) {
131 | // spacerHeights.inner = inner.unadjustedHeight;
132 | // spacerHeights.outer = outer.height
133 | // return spacerHeights;
134 | // }
135 | // if (outer.unadjustedHeightAlt/exConst <= spacerHeights.start) {
136 | // spacerHeights.outer = outer.unadjustedHeight;
137 | // spacerHeights.inner = inner.height;
138 | // return spacerHeights;
139 | // }
140 | // else {
141 | // return Error("Inner Spacer Error");
142 | // }
143 | // };
144 | // //If Double=Wrap
145 | // if (perHeight[0].name === "main"){
146 | // console.log("Double-Wrap");
147 | // spacerHeights.inner = main.height;
148 | // spacerHeights.outer = main.height;
149 | //
150 | // const brDifference = perHeight[1].brCount - perHeight[0].brCount;
151 | // spacerHeights.end = brDifference*perHeight[1].lineHeight;
152 | // return spacerHeights;
153 | // }
154 | //
155 | // //If Stairs
156 | // if (perHeight[1].name === "main") {
157 | // console.log("Stairs");
158 | // spacerHeights[perHeight[0].name] = perHeight[0].height;
159 | // spacerHeights[perHeight[2].name] = main.height;
160 | // return spacerHeights;
161 | // }
162 | //
163 | // //If Double Extend
164 | // console.log("Double-Extend")
165 | // spacerHeights.inner = inner.height + (inner.height/inner.height**2)*inner.lineHeight;
166 | // spacerHeights.outer = outer.height + (inner.height/inner.height**2)*outer.lineHeight;
167 | // return spacerHeights
168 | // }
169 |
170 |
171 | // We could probably put this somewhere else, it was meant to be a place for all the padding corrections,
172 | // but there turned out to only be one
173 | const paddingAreas = {
174 | name: "paddingAreas",
175 | horizontalSide: sideWidth * parsedOptions.padding.vertical,
176 | };
177 |
178 |
179 | const topArea = (lineHeight) => ((4 * lineHeight * topWidth)); //remove area of the top 4 lines
180 |
181 |
182 | const main = {
183 | name: "main",
184 | width: midWidth,
185 | text: mainText,
186 | lineHeight: parsedOptions.lineHeight.main,
187 | area: getAreaOfText(mainText, parsedOptions.fontFamily.main, parsedOptions.fontSize.main, midWidth, parsedOptions.lineHeight.main, dummy),
188 | length: null,
189 | height: null,
190 | };
191 | const outer = {
192 | name: "outer",
193 | width: sideWidth,
194 | text: outerText,
195 | lineHeight: parsedOptions.lineHeight.side,
196 | area: getAreaOfText(outerText, parsedOptions.fontFamily.outer, parsedOptions.fontSize.side, sideWidth, parsedOptions.lineHeight.side, dummy)
197 | - topArea(parsedOptions.lineHeight.side),
198 | length: null,
199 | height: null,
200 | };
201 | const inner = {
202 | name: "inner",
203 | width: sideWidth,
204 | text: innerText,
205 | lineHeight: parsedOptions.lineHeight.side,
206 | area:
207 | getAreaOfText(innerText, parsedOptions.fontFamily.inner, parsedOptions.fontSize.side, sideWidth, parsedOptions.lineHeight.side, dummy)
208 | - topArea(parsedOptions.lineHeight.side),
209 | length: null,
210 | height: null,
211 | };
212 |
213 | const texts = [main, outer, inner];
214 | texts.forEach(text => text.height = text.area / text.width);
215 | texts.forEach(text => text.unadjustedArea = text.area + topArea(parsedOptions.lineHeight.side));
216 | texts.forEach(text => text.unadjustedHeight = text.unadjustedArea / text.width);
217 |
218 | const perHeight = Array.from(texts).sort((a, b) => a.height - b.height);
219 |
220 | //There are Three Main Types of Case:
221 | //Double-Wrap: The main text being the smallest and commentaries wrapping around it
222 | //Stairs: The main text wrapping around one, but the other wrapping around it
223 | //Double-Extend: The main text wrapping around both commentaries
224 |
225 | //Main Text is Smallest: Double-Wrap
226 | //Main Text being Middle: Stairs
227 | //Main Text Being Largest: Double-Extend
228 |
229 | //First we need to check we have enough commentary to fill the first four lines
230 | if (inner.height <= 0 && outer.height <= 0){
231 | console.error("No Commentary");
232 | return Error("No Commentary");
233 | }
234 |
235 | // This is a case that we have to decice what to do with, when there is not enough commentary on both sides to fill the lines.
236 | if (inner.height <= spacerHeights.start && outer.height <= spacerHeights.start) {
237 | console.error("Not Enough Commentary to Fill Four Lines");
238 | return Error("Not Enough Commentary");
239 | }
240 | // We are going to deal with our first edge case when there is either only one commentary
241 | // Or where there is enough of one commentary, but not four lines of the other.
242 | if (inner.unadjustedHeight <= spacerHeights.start || outer.unadjustedHeight <= spacerHeights.start) {
243 | if (inner.unadjustedHeight <= spacerHeights.start) {
244 | spacerHeights.inner = inner.unadjustedHeight;
245 | spacerHeights.outer = (outer.unadjustedArea - parsedOptions.width * 4 * parsedOptions.lineHeight.side) / sideWidth;
246 | spacerHeights.exception = 1;
247 | return spacerHeights;
248 | }
249 | if (outer.unadjustedHeight <= spacerHeights.start) {
250 | spacerHeights.outer = outer.unadjustedHeight;
251 |
252 | spacerHeights.inner = (inner.unadjustedArea - parsedOptions.width * 4 * parsedOptions.lineHeight.side) / sideWidth;
253 | spacerHeights.exception = 2;
254 | return spacerHeights;
255 | }
256 | else {
257 | return Error("Inner Spacer Error");
258 | }
259 | }
260 | //If Double=Wrap
261 | if (perHeight[0].name === "main"){
262 | console.log("Double-Wrap");
263 | spacerHeights.inner = main.area/midWidth;
264 | spacerHeights.outer = spacerHeights.inner;
265 |
266 | const sideArea = spacerHeights.inner * sideWidth + paddingAreas.horizontalSide;
267 | const bottomChunk = perHeight[1].area - sideArea;
268 | const bottomHeight = bottomChunk / topWidth;
269 | spacerHeights.end = bottomHeight;
270 | return spacerHeights;
271 | }
272 | // If Stairs, there's one text at the bottom. We will call it THE stair.
273 | // The remaining two texts form a "block" that we must compare with that bottom text.
274 | const blockArea = (main.area + perHeight[0].area);
275 | const blockWidth = midWidth + sideWidth;
276 | const blockHeight = blockArea / blockWidth;
277 |
278 | const stair = (perHeight[1].name == "main") ? perHeight[2] : perHeight[1];
279 | const stairHeight = stair.area / stair.width;
280 |
281 | if (blockHeight < stairHeight) {
282 | console.log(`Stairs, ${stair.name} is the stair`);
283 | // This function accounts for extra space that is introduced by padding
284 | const lilArea = (height1, height2, horizPadding) => (horizPadding) * (height1 - height2);
285 | const smallest = perHeight[0];
286 | spacerHeights[smallest.name] = smallest.height;
287 | spacerHeights[stair.name] = (blockArea - lilArea(blockHeight, spacerHeights[smallest.name], parsedOptions.padding.horizontal)) / blockWidth;
288 | return spacerHeights
289 | }
290 | //If Double Extend
291 | console.log("Double-Extend");
292 | spacerHeights.inner = inner.height;
293 | spacerHeights.outer = outer.height;
294 |
295 | return spacerHeights
296 | }
297 |
298 | function styleInject(css, ref) {
299 | if ( ref === void 0 ) ref = {};
300 | var insertAt = ref.insertAt;
301 |
302 | if (!css || typeof document === 'undefined') { return; }
303 |
304 | var head = document.head || document.getElementsByTagName('head')[0];
305 | var style = document.createElement('style');
306 | style.type = 'text/css';
307 |
308 | if (insertAt === 'top') {
309 | if (head.firstChild) {
310 | head.insertBefore(style, head.firstChild);
311 | } else {
312 | head.appendChild(style);
313 | }
314 | } else {
315 | head.appendChild(style);
316 | }
317 |
318 | if (style.styleSheet) {
319 | style.styleSheet.cssText = css;
320 | } else {
321 | style.appendChild(document.createTextNode(css));
322 | }
323 | }
324 |
325 | var css_248z = "/*Keep this as the first rule in the file*/\n.styles_dafRoot__1QUlM {\n --contentWidth: 0px;\n --padding-horizontal: 0px;\n --padding-vertical: 0px;\n --halfway: 50%;\n\n --fontFamily-inner: \"Rashi\";\n --fontFamily-outer: \"Tosafot\";\n --fontFamily-main: \"Vilna\";\n --direction: \"rtl\";\n\n --fontSize-main: 0px;\n --fontSize-side: 0px;\n\n --lineHeight-main: 0px;\n --lineHeight-side: 0px;\n\n --mainWidth: 0%;\n --mainMargin-start: var(--mainWidth);\n --sidePercent: calc(calc(100% - var(--mainMargin-start)) / 2);\n --remainderPercent: calc(100% - var(--sidePercent));\n\n --innerFloat: left;\n --outerFloat: right;\n\n --spacerHeights-start: 0px;\n --spacerHeights-outer: 0px;\n --spacerHeights-inner: 0px;\n --spacerHeights-end: 0px;\n\n /*Edge Cases*/\n --hasInnerStartGap: 0;\n --hasOuterStartGap: 0;\n --innerStartWidth: 50%;\n --innerPadding: 0px;\n --outerStartWidth: 50%;\n --outerPadding: 0px;\n}\n\n/*Containers*/\n.styles_dafRoot__1QUlM,\n.styles_outer__abXQX,\n.styles_inner__x-amJ,\n.styles_main__BHTRd {\n width: var(--contentWidth);\n pointer-events: none;\n box-sizing: content-box;\n}\n\n.styles_outer__abXQX, .styles_inner__x-amJ, .styles_main__BHTRd {\n position: absolute;\n}\n\n/*Float changes with amud*/\n.styles_inner__x-amJ .styles_spacer__2T7TS,\n.styles_main__BHTRd .styles_spacer__2T7TS.styles_outerMid__2WtcY {\n float: var(--innerFloat);\n}\n\n.styles_outer__abXQX .styles_spacer__2T7TS,\n.styles_main__BHTRd .styles_spacer__2T7TS.styles_innerMid__27MCi {\n float: var(--outerFloat);\n}\n\n/*Spacer widths determined by options*/\n.styles_inner__x-amJ .styles_spacer__2T7TS,\n.styles_outer__abXQX .styles_spacer__2T7TS {\n width: var(--halfway);\n}\n.styles_spacer__2T7TS.styles_mid__dcgUr {\n width: var(--remainderPercent);\n}\n\n.styles_main__BHTRd .styles_spacer__2T7TS.styles_start__AwkfY {\n width: var(--contentWidth);\n}\n.styles_main__BHTRd .styles_spacer__2T7TS.styles_innerMid__27MCi,\n.styles_main__BHTRd .styles_spacer__2T7TS.styles_outerMid__2WtcY {\n width: var(--sidePercent);\n}\n\n/*Spacer heights determined by algorithm*/\n.styles_spacer__2T7TS.styles_start__AwkfY {\n height: var(--spacerHeights-start);\n}\n\n.styles_spacer__2T7TS.styles_end__2wr6A {\n height: var(--spacerHeights-end);\n}\n\n.styles_inner__x-amJ .styles_spacer__2T7TS.styles_mid__dcgUr,\n.styles_main__BHTRd .styles_spacer__2T7TS.styles_innerMid__27MCi {\n height: var(--spacerHeights-inner);\n}\n.styles_outer__abXQX .styles_spacer__2T7TS.styles_mid__dcgUr,\n.styles_main__BHTRd .styles_spacer__2T7TS.styles_outerMid__2WtcY {\n height: var(--spacerHeights-outer);\n}\n\n/*Settings to handle edge Cases*/\n\n.styles_inner__x-amJ .styles_spacer__2T7TS.styles_start__AwkfY {\n width: var(--innerStartWidth);\n margin-left: var(--innerPadding);\n margin-right: var(--innerPadding);\n}\n\n.styles_outer__abXQX .styles_spacer__2T7TS.styles_start__AwkfY {\n width: var(--outerStartWidth);\n margin-left: var(--outerPadding);\n margin-right: var(--outerPadding);\n}\n\n.styles_inner__x-amJ .styles_spacer__2T7TS.styles_start__AwkfY{\n margin-bottom: calc(var(--padding-vertical) * var(--hasInnerStartGap));\n}\n.styles_outer__abXQX .styles_spacer__2T7TS.styles_start__AwkfY {\n margin-bottom: calc(var(--padding-vertical) * var(--hasOuterStartGap));\n}\n\n.styles_spacer__2T7TS.styles_mid__dcgUr {\n clear: both;\n}\n\n/*Margins!*/\n.styles_spacer__2T7TS.styles_start__AwkfY,\n.styles_spacer__2T7TS.styles_end__2wr6A,\n.styles_main__BHTRd .styles_spacer__2T7TS.styles_innerMid__27MCi,\n.styles_main__BHTRd .styles_spacer__2T7TS.styles_outerMid__2WtcY {\n margin-left: calc(0.5 * var(--padding-horizontal));\n margin-right: calc(0.5 * var(--padding-horizontal));\n}\n\n.styles_spacer__2T7TS.styles_mid__dcgUr,\n.styles_main__BHTRd .styles_text__1_7-z {\n margin-top: var(--padding-vertical);\n}\n\n.styles_spacer__2T7TS.styles_mid__dcgUr,\n.styles_main__BHTRd .styles_spacer__2T7TS.styles_innerMid__27MCi,\n.styles_main__BHTRd .styles_spacer__2T7TS.styles_outerMid__2WtcY {\n margin-bottom: var(--padding-vertical);\n}\n\n/*Text*/\n.styles_text__1_7-z {\n direction: var(--direction);\n text-align: justify;\n text-align-last: center;\n}\n\n.styles_text__1_7-z span {\n pointer-events: auto;\n}\n\n.styles_main__BHTRd .styles_text__1_7-z {\n font-family: var(--fontFamily-main);\n font-size: var(--fontSize-main);\n line-height: var(--lineHeight-main);\n}\n\n.styles_inner__x-amJ .styles_text__1_7-z,\n.styles_outer__abXQX .styles_text__1_7-z {\n font-size: var(--fontSize-side);\n line-height: var(--lineHeight-side);\n}\n\n.styles_inner__x-amJ .styles_text__1_7-z {\n font-family: var(--fontFamily-inner);\n}\n\n.styles_outer__abXQX .styles_text__1_7-z {\n font-family: var(--fontFamily-outer);\n}\n";
326 | var classes = {"dafRoot":"styles_dafRoot__1QUlM","outer":"styles_outer__abXQX","inner":"styles_inner__x-amJ","main":"styles_main__BHTRd","spacer":"styles_spacer__2T7TS","outerMid":"styles_outerMid__2WtcY","innerMid":"styles_innerMid__27MCi","mid":"styles_mid__dcgUr","start":"styles_start__AwkfY","end":"styles_end__2wr6A","text":"styles_text__1_7-z"};
327 | styleInject(css_248z);
328 |
329 | const sideSpacersClasses = {
330 | start: [classes.spacer, classes.start],
331 | mid: [classes.spacer, classes.mid],
332 | end: [classes.spacer, classes.end]
333 | };
334 |
335 | const containerClasses = {
336 | el: classes.dafRoot,
337 | outer: {
338 | el: classes.outer,
339 | spacers: sideSpacersClasses,
340 | text: classes.text,
341 | },
342 | inner: {
343 | el: classes.inner,
344 | spacers: sideSpacersClasses,
345 | text: classes.text,
346 | },
347 | main: {
348 | el: classes.main,
349 | spacers: {
350 | start: sideSpacersClasses.start,
351 | inner: [classes.spacer, classes.innerMid],
352 | outer: [classes.spacer, classes.outerMid]
353 | },
354 | text: classes.text
355 | }
356 | };
357 |
358 | function addClasses(element, classNames) {
359 | if (Array.isArray(classNames))
360 | element.classList.add(...classNames);
361 | else
362 | element.classList.add(classNames);
363 | }
364 |
365 | function setVars(object, prefix = "") {
366 | const varsRule = Array.prototype.find.call
367 | (document.styleSheets, sheet => sheet.rules[0].selectorText == `.${classes.dafRoot}`)
368 | .rules[0];
369 |
370 | Object.entries(object).forEach(([key, value]) => {
371 | if (typeof value == "string") {
372 | varsRule.style.setProperty(`--${prefix}${key}`, value);
373 | } else if (typeof value == "object") {
374 | setVars(value, `${key}-`);
375 | }
376 | });
377 | }
378 |
379 |
380 | let appliedOptions;
381 | var styleManager = {
382 | applyClasses(containers, classesMap = containerClasses) {
383 | for (const key in containers) {
384 | if (key in classesMap) {
385 | const value = classesMap[key];
386 | if (typeof value === "object" && !Array.isArray(value)) {
387 | this.applyClasses(containers[key], value);
388 | } else {
389 | addClasses(containers[key], value);
390 | }
391 | }
392 | }
393 | },
394 | updateOptionsVars(options) {
395 | appliedOptions = options;
396 | setVars(options);
397 | },
398 | updateSpacersVars(spacerHeights) {
399 | setVars(
400 | Object.fromEntries(
401 | Object.entries(spacerHeights).map(
402 | ([key, value]) => ([key, String(value) + 'px']))
403 | ),
404 | "spacerHeights-"
405 | );
406 | },
407 | updateIsAmudB(amudB) {
408 | setVars({
409 | innerFloat: amudB ? "right" : "left",
410 | outerFloat: amudB ? "left" : "right"
411 | });
412 | },
413 | manageExceptions(spacerHeights) {
414 | if (!spacerHeights.exception) {
415 | setVars({
416 | hasOuterStartGap: "0",
417 | hasInnerStartGap: "0",
418 | outerStartWidth: "50%",
419 | innerStartWidth: "50%",
420 | innerPadding: appliedOptions.innerPadding,
421 | outerPadding: appliedOptions.outerPadding,
422 | });
423 | return;
424 | }
425 | if (spacerHeights.exception === 1) {
426 | console.log("In Style Exception, Case 1");
427 | setVars({
428 | hasInnerStartGap: "1",
429 | innerStartWidth: "100%",
430 | outerStartWidth: "0%",
431 | innerPadding: "0px",
432 | outerPadding: "0px",
433 | });
434 | } else if (spacerHeights.exception === 2) {
435 | console.log("In Style Exception, Case 2");
436 | setVars({
437 | hasOuterStartGap: "1",
438 | outerStartWidth: "100%",
439 | innerStartWidth: "0%",
440 | innerPadding: "0px",
441 | outerPadding: "0px"
442 | });
443 | }
444 | }
445 | };
446 |
447 | function getLineInfo(text, font, fontSize, lineHeight, dummy) {
448 | dummy.innerHTML = "";
449 | let testDiv = document.createElement("span");
450 | testDiv.style.font = fontSize + " " + String(font);
451 | testDiv.style.lineHeight = String(lineHeight) + "px";
452 | testDiv.innerHTML = text;
453 | testDiv.style.position = "absolute";
454 | dummy.append(testDiv);
455 | const rect = testDiv.getBoundingClientRect();
456 | const height = rect.height;
457 | const width = rect.width;
458 | const widthProportional = width / dummy.getBoundingClientRect().width;
459 | testDiv.remove();
460 | return {height, width, widthProportional};
461 | }
462 |
463 | function heightAccumulator(font, fontSize, lineHeight, dummy) {
464 | return (lines) => {
465 | return getLineInfo(lines.join("
"), font, fontSize, lineHeight, dummy).height;
466 | }
467 | }
468 |
469 | function getBreaks(sizeArray) {
470 | const widths = sizeArray.map(size => size.widthProportional);
471 | const diffs = widths.map((width, index, widths) => index == 0 ? 0 : Math.abs(width - widths[index - 1]));
472 | const threshold = 0.12;
473 | let criticalPoints = diffs.reduce((indices, curr, currIndex) => {
474 | //Breaks before line 4 are flukes
475 | if (currIndex < 4) return indices;
476 | if (curr > threshold) {
477 | //There should never be two breakpoints in a row
478 | const prevIndex = indices[indices.length - 1];
479 | if (prevIndex && (currIndex - prevIndex) == 1) {
480 | return indices;
481 | }
482 | indices.push(currIndex);
483 | }
484 | return indices;
485 | }, []);
486 | const averageAround = points => points.map((point, i) => {
487 | let nextPoint;
488 | if (!nextPoint) {
489 | nextPoint = Math.min(point + 3, widths.length - 1);
490 | }
491 | let prevPoint;
492 | if (!prevPoint) {
493 | prevPoint = Math.max(point - 3, 0);
494 | }
495 | /*
496 | Note that these are divided by the width of the critical point line such that
497 | we get the average width of the preceeding and proceeding chunks *relative*
498 | to the critical line.
499 | */
500 | const before = (widths.slice(prevPoint, point).reduce((acc, curr) => acc + curr) /
501 | (point - prevPoint)) / widths[point];
502 | let after;
503 | if ( point + 1 >= nextPoint) {
504 | after = widths[nextPoint] / widths[point];
505 | } else {
506 | after =(widths.slice(point + 1, nextPoint).reduce((acc, curr) => acc + curr) /
507 | (nextPoint - point - 1)) / widths[point];
508 | }
509 | return {
510 | point,
511 | before,
512 | after,
513 | diff: Math.abs(after - before)
514 | }
515 | });
516 | const aroundDiffs = averageAround(criticalPoints)
517 | .sort( (a,b) => b.diff - a.diff);
518 | criticalPoints = aroundDiffs
519 | .filter( ({diff}) => diff > 0.22)
520 | .map( ({point}) => point);
521 | return criticalPoints.sort( (a, b) => a - b);
522 | }
523 |
524 | function onlyOneCommentary(lines, options, dummy) {
525 | const fontFamily = options.fontFamily.inner;
526 | const fontSize = options.fontSize.side;
527 | const lineHeight = parseFloat(options.lineHeight.side);
528 | const sizes = lines.map(text => getLineInfo(text, fontFamily, fontSize, lineHeight, dummy));
529 | const breaks = getBreaks(sizes);
530 | if (breaks.length == 3) {
531 | const first = lines.slice(0, breaks[1]);
532 | const second = lines.slice(breaks[1]);
533 | return [first, second];
534 | }
535 | }
536 |
537 | function calculateSpacersBreaks(mainArray, rashiArray, tosafotArray, options, dummy) {
538 | const lines = {
539 | main: mainArray,
540 | rashi: rashiArray,
541 | tosafot: tosafotArray
542 | };
543 |
544 | const parsedOptions = {
545 | padding: {
546 | vertical: parseFloat(options.padding.vertical),
547 | horizontal: parseFloat(options.padding.horizontal)
548 | },
549 | halfway: 0.01 * parseFloat(options.halfway),
550 | fontFamily: options.fontFamily, // Object of strings
551 | fontSize: {
552 | main: options.fontSize.main,
553 | side: options.fontSize.side,
554 | },
555 | lineHeight: {
556 | main: parseFloat(options.lineHeight.main),
557 | side: parseFloat(options.lineHeight.side),
558 | },
559 | };
560 |
561 |
562 | const mainOptions = [parsedOptions.fontFamily.main, parsedOptions.fontSize.main, parsedOptions.lineHeight.main];
563 | const commentaryOptions = [parsedOptions.fontFamily.inner, parsedOptions.fontSize.side, parsedOptions.lineHeight.side];
564 |
565 | const sizes = {};
566 | sizes.main = lines.main.map(text => getLineInfo(text, ...mainOptions, dummy));
567 | ["rashi", "tosafot"].forEach(text => {
568 | sizes[text] = lines[text].map(line => getLineInfo(line, ...commentaryOptions, dummy));
569 | });
570 |
571 | const accumulateMain = heightAccumulator(...mainOptions, dummy);
572 | const accumulateCommentary = heightAccumulator(...commentaryOptions, dummy);
573 |
574 | const breaks = {};
575 |
576 | ["rashi", "tosafot", "main"].forEach(text => {
577 | breaks[text] = getBreaks(sizes[text])
578 | /*
579 | Hadran lines aren't real candidates for line breaks.
580 | TODO: Extract this behavior , give it an option/parameter
581 | */
582 | .filter(lineNum => !(lines[text][lineNum].includes("hadran"))
583 | );
584 | });
585 |
586 |
587 | const spacerHeights = {
588 | start: 4.4 * parsedOptions.lineHeight.side,
589 | inner: null,
590 | outer: null,
591 | end: 0,
592 | exception: 0
593 | };
594 |
595 | const mainHeight = accumulateMain(lines.main);
596 | const mainHeightOld = (sizes.main.length) * parsedOptions.lineHeight.main;
597 | let afterBreak = {
598 | inner: accumulateCommentary(lines.rashi.slice(4)),
599 | outer: accumulateCommentary(lines.tosafot.slice(4))
600 | };
601 |
602 | let afterBreakOld = {
603 | inner: parsedOptions.lineHeight.side * (sizes.rashi.length - 4),
604 | outer: parsedOptions.lineHeight.side * (sizes.tosafot.length - 4)
605 | };
606 |
607 | if (breaks.rashi.length < 1 || breaks.tosafot.length < 1) {
608 | console.log("Dealing with Exceptions");
609 | if (breaks.rashi.length < 1) {
610 | afterBreak.inner = parsedOptions.lineHeight.side * (sizes.rashi.length + 1);
611 | spacerHeights.exception = 2;
612 | }
613 | if (breaks.tosafot.length < 1) {
614 | afterBreak.outer = parsedOptions.lineHeight.side * (sizes.tosafot.length + 1);
615 | spacerHeights.exception = 2;
616 | }
617 | }
618 | switch (breaks.main.length) {
619 | case 0:
620 | spacerHeights.inner = mainHeight;
621 | spacerHeights.outer = mainHeight;
622 | if (breaks.rashi.length == 2) {
623 | spacerHeights.end = accumulateCommentary(lines.rashi.slice(breaks.rashi[1]));
624 | } else {
625 | spacerHeights.end = accumulateCommentary(lines.tosafot.slice(breaks.tosafot[1]));
626 | }
627 | console.log("Double wrap");
628 | break;
629 | case 1:
630 | if (breaks.rashi.length != breaks.tosafot.length) {
631 | if (breaks.tosafot.length == 0) {
632 | spacerHeights.outer = 0;
633 | spacerHeights.inner = afterBreak.inner;
634 | break;
635 | }
636 | if (breaks.rashi.length == 0) {
637 | spacerHeights.inner = 0;
638 | spacerHeights.outer = afterBreak.outer;
639 | break;
640 | }
641 | let stair;
642 | let nonstair;
643 | if (breaks.rashi.length == 1) {
644 | stair = "outer";
645 | nonstair = "inner";
646 | } else {
647 | stair = "inner";
648 | nonstair = "outer";
649 | }
650 | spacerHeights[nonstair] = afterBreak[nonstair];
651 | spacerHeights[stair] = mainHeight;
652 | console.log("Stairs");
653 | break;
654 | }
655 | case 2:
656 | spacerHeights.inner = afterBreak.inner;
657 | spacerHeights.outer = afterBreak.outer;
658 | console.log("Double Extend");
659 | break;
660 | default:
661 | spacerHeights.inner = afterBreak.inner;
662 | spacerHeights.outer = afterBreak.outer;
663 | console.log("No Case Exception");
664 | break;
665 | }
666 | return spacerHeights;
667 | }
668 |
669 | function el(tag, parent) {
670 | const newEl = document.createElement(tag);
671 | if (parent) parent.append(newEl);
672 | return newEl;
673 | }
674 |
675 | function div(parent) {
676 | return el("div", parent);
677 | }
678 |
679 | function span(parent) {
680 | return el("span", parent);
681 | }
682 |
683 |
684 | function renderer (el, options = defaultOptions) {
685 | const root = (typeof el === "string") ? document.querySelector(el) : el;
686 | if (!(root && root instanceof Element && root.tagName.toUpperCase() === "DIV")) {
687 | throw "Argument must be a div element or its selector"
688 | }
689 | const outerContainer = div(root);
690 | const innerContainer = div(root);
691 | const mainContainer = div(root);
692 | const dummy = div(root);
693 | dummy.id = "dummy";
694 | const containers = {
695 | el: root,
696 | dummy: dummy,
697 | outer: {
698 | el: outerContainer,
699 | spacers: {
700 | start: div(outerContainer),
701 | mid: div(outerContainer),
702 | end: div(outerContainer)
703 | },
704 | text: div(outerContainer)
705 | },
706 | inner: {
707 | el: innerContainer,
708 | spacers: {
709 | start: div(innerContainer),
710 | mid: div(innerContainer),
711 | end: div(innerContainer)
712 | },
713 | text: div(innerContainer)
714 | },
715 | main: {
716 | el: mainContainer,
717 | spacers: {
718 | start: div(mainContainer),
719 | inner: div(mainContainer),
720 | outer: div(mainContainer),
721 | },
722 | text: div(mainContainer)
723 | }
724 | };
725 |
726 | const textSpans = {
727 | main: span(containers.main.text),
728 | inner: span(containers.inner.text),
729 | outer: span(containers.outer.text)
730 | };
731 |
732 | const clonedOptions = mergeAndClone(options, defaultOptions);
733 |
734 | styleManager.applyClasses(containers);
735 | styleManager.updateOptionsVars(clonedOptions);
736 |
737 | let resizeEvent;
738 | return {
739 | classes,
740 | containers,
741 | spacerHeights: {
742 | start: 0,
743 | inner: 0,
744 | outer: 0,
745 | end: 0
746 | },
747 | amud: "a",
748 | render(main, inner, outer, amud = "a", linebreak, renderCallback, resizeCallback) {
749 | if (resizeEvent) {
750 | window.removeEventListener("resize", resizeEvent);
751 | }
752 | if (this.amud != amud) {
753 | this.amud = amud;
754 | styleManager.updateIsAmudB(amud == "b");
755 | }
756 | if (!linebreak) {
757 | this.spacerHeights = calculateSpacers(main, inner, outer, clonedOptions, containers.dummy);
758 | resizeEvent = () => {
759 | this.spacerHeights = calculateSpacers(main, inner, outer, clonedOptions, containers.dummy);
760 | styleManager.updateSpacersVars(this.spacerHeights);
761 | console.log("resizing");
762 | if (resizeCallback)
763 | resizeCallback();
764 | };
765 | window.addEventListener("resize", resizeEvent);
766 | }
767 | else {
768 | let [mainSplit, innerSplit, outerSplit] = [main, inner, outer].map( text => {
769 | containers.dummy.innerHTML = text;
770 | const divRanges = Array.from(containers.dummy.querySelectorAll("div")).map(div => {
771 | const range = document.createRange();
772 | range.selectNode(div);
773 | return range;
774 | });
775 |
776 | const brs = containers.dummy.querySelectorAll(linebreak);
777 | const splitFragments = [];
778 | brs.forEach((node, index) => {
779 | const range = document.createRange();
780 | range.setEndBefore(node);
781 | if (index == 0) {
782 | range.setStart(containers.dummy, 0);
783 | } else {
784 | const prev = brs[index - 1];
785 | range.setStartAfter(prev);
786 | }
787 | divRanges.forEach( (divRange, i) => {
788 | const inBetween = range.compareBoundaryPoints(Range.START_TO_START, divRange) < 0 && range.compareBoundaryPoints(Range.END_TO_END, divRange) > 0;
789 | if (inBetween) {
790 | splitFragments.push(divRange.extractContents());
791 | divRanges.splice(i, 1);
792 | }
793 | });
794 |
795 | splitFragments.push(range.extractContents());
796 | });
797 |
798 | return splitFragments.map(fragment => {
799 | const el = document.createElement("div");
800 | el.append(fragment);
801 | return el.innerHTML;
802 | })
803 | });
804 |
805 | containers.dummy.innerHTML = "";
806 |
807 | const hasInner = innerSplit.length != 0;
808 | const hasOuter = outerSplit.length != 0;
809 |
810 | if (hasInner != hasOuter) {
811 | const withText = hasInner ? innerSplit : outerSplit;
812 | const fixed = onlyOneCommentary(withText, clonedOptions, dummy);
813 | if (fixed) {
814 | if (amud == "a") {
815 | innerSplit = fixed[0];
816 | outerSplit = fixed[1];
817 | } else {
818 | innerSplit = fixed[1];
819 | outerSplit = fixed[0];
820 | }
821 | inner = innerSplit.join('
');
822 | outer = outerSplit.join('
');
823 | }
824 | }
825 |
826 | this.spacerHeights = calculateSpacersBreaks(mainSplit, innerSplit, outerSplit, clonedOptions, containers.dummy);
827 | resizeEvent = () => {
828 | this.spacerHeights = calculateSpacersBreaks(mainSplit, innerSplit, outerSplit, clonedOptions, containers.dummy);
829 | styleManager.updateSpacersVars(this.spacerHeights);
830 | if (resizeCallback)
831 | resizeCallback();
832 | console.log("resizing");
833 | };
834 | window.addEventListener('resize', resizeEvent);
835 | }
836 | styleManager.updateSpacersVars(this.spacerHeights);
837 | styleManager.manageExceptions(this.spacerHeights);
838 | textSpans.main.innerHTML = main;
839 | textSpans.inner.innerHTML = inner;
840 | textSpans.outer.innerHTML = outer;
841 |
842 | const containerHeight = Math.max(...["main", "inner", "outer"].map(t => containers[t].el.offsetHeight));
843 | containers.el.style.height = `${containerHeight}px`;
844 | if (renderCallback)
845 | renderCallback();
846 | },
847 | }
848 | }
849 |
850 | export default renderer;
851 |
--------------------------------------------------------------------------------
/dist/daf-renderer.min.js:
--------------------------------------------------------------------------------
1 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).dafRenderer=t()}(this,(function(){"use strict";const e={contentWidth:"600px",mainWidth:"50%",padding:{vertical:"10px",horizontal:"16px"},innerPadding:"4px",outerPadding:"4px",halfway:"50%",fontFamily:{inner:"Rashi",outer:"Rashi",main:"Vilna"},direction:"rtl",fontSize:{main:"15px",side:"10.5px"},lineHeight:{main:"17px",side:"14px"}};function t(n,i=e){const a={};for(const e in i)if(e in n){const s=typeof i[e];typeof n[e]!==s&&console.error(`Option ${e} must be of type ${s}; ${typeof n[e]} was passed.`),a[e]="object"==s?t(n[e],i[e]):n[e]}else a[e]=i[e];return a}function n(e,t,n,i,a,s){let r=document.createElement("div");r.style.font=String(n)+"px "+String(t),r.style.width=String(i)+"px",r.style.lineHeight=String(a)+"px",r.innerHTML=e,s.append(r);let o=Number(r.clientHeight*r.clientWidth);return r.remove(),o}function i(e,t,i,a,s){const r={width:parseFloat(a.contentWidth),padding:{vertical:parseFloat(a.padding.vertical),horizontal:parseFloat(a.padding.horizontal)},halfway:.01*parseFloat(a.halfway),fontFamily:a.fontFamily,fontSize:{main:parseFloat(a.fontSize.main),side:parseFloat(a.fontSize.side)},lineHeight:{main:parseFloat(a.lineHeight.main),side:parseFloat(a.lineHeight.side)},mainWidth:.01*parseFloat(a.mainWidth)},o=Number(r.width*r.mainWidth)-2*r.padding.horizontal,l=Number(r.width*r.halfway)-r.padding.horizontal,d=Number(r.width*(1-r.mainWidth)/2),_={start:4.3*r.lineHeight.side,inner:null,outer:null,end:0,exception:0},h=d*r.padding.vertical,c=e=>4*e*l,p={name:"main",width:o,text:e,lineHeight:r.lineHeight.main,area:n(e,r.fontFamily.main,r.fontSize.main,o,r.lineHeight.main,s),length:null,height:null},m={name:"outer",width:d,text:i,lineHeight:r.lineHeight.side,area:n(i,r.fontFamily.outer,r.fontSize.side,d,r.lineHeight.side,s)-c(r.lineHeight.side),length:null,height:null},u={name:"inner",width:d,text:t,lineHeight:r.lineHeight.side,area:n(t,r.fontFamily.inner,r.fontSize.side,d,r.lineHeight.side,s)-c(r.lineHeight.side),length:null,height:null},g=[p,m,u];g.forEach((e=>e.height=e.area/e.width)),g.forEach((e=>e.unadjustedArea=e.area+c(r.lineHeight.side))),g.forEach((e=>e.unadjustedHeight=e.unadjustedArea/e.width));const y=Array.from(g).sort(((e,t)=>e.height-t.height));if(u.height<=0&&m.height<=0)return console.error("No Commentary"),Error("No Commentary");if(u.height<=_.start&&m.height<=_.start)return console.error("Not Enough Commentary to Fill Four Lines"),Error("Not Enough Commentary");if(u.unadjustedHeight<=_.start||m.unadjustedHeight<=_.start)return u.unadjustedHeight<=_.start?(_.inner=u.unadjustedHeight,_.outer=(m.unadjustedArea-4*r.width*r.lineHeight.side)/d,_.exception=1,_):m.unadjustedHeight<=_.start?(_.outer=m.unadjustedHeight,_.inner=(u.unadjustedArea-4*r.width*r.lineHeight.side)/d,_.exception=2,_):Error("Inner Spacer Error");if("main"===y[0].name){console.log("Double-Wrap"),_.inner=p.area/o,_.outer=_.inner;const e=_.inner*d+h,t=(y[1].area-e)/l;return _.end=t,_}const f=p.area+y[0].area,x=o+d,T=f/x,S="main"==y[1].name?y[2]:y[1];if(Tn*(e-t),t=y[0];return _[t.name]=t.height,_[S.name]=(f-e(T,_[t.name],r.padding.horizontal))/x,_}return console.log("Double-Extend"),_.inner=u.height,_.outer=m.height,_}var a={dafRoot:"styles_dafRoot__1QUlM",outer:"styles_outer__abXQX",inner:"styles_inner__x-amJ",main:"styles_main__BHTRd",spacer:"styles_spacer__2T7TS",outerMid:"styles_outerMid__2WtcY",innerMid:"styles_innerMid__27MCi",mid:"styles_mid__dcgUr",start:"styles_start__AwkfY",end:"styles_end__2wr6A",text:"styles_text__1_7-z"};!function(e,t){void 0===t&&(t={});var n=t.insertAt;if(e&&"undefined"!=typeof document){var i=document.head||document.getElementsByTagName("head")[0],a=document.createElement("style");a.type="text/css","top"===n&&i.firstChild?i.insertBefore(a,i.firstChild):i.appendChild(a),a.styleSheet?a.styleSheet.cssText=e:a.appendChild(document.createTextNode(e))}}('/*Keep this as the first rule in the file*/\n.styles_dafRoot__1QUlM {\n --contentWidth: 0px;\n --padding-horizontal: 0px;\n --padding-vertical: 0px;\n --halfway: 50%;\n\n --fontFamily-inner: "Rashi";\n --fontFamily-outer: "Tosafot";\n --fontFamily-main: "Vilna";\n --direction: "rtl";\n\n --fontSize-main: 0px;\n --fontSize-side: 0px;\n\n --lineHeight-main: 0px;\n --lineHeight-side: 0px;\n\n --mainWidth: 0%;\n --mainMargin-start: var(--mainWidth);\n --sidePercent: calc(calc(100% - var(--mainMargin-start)) / 2);\n --remainderPercent: calc(100% - var(--sidePercent));\n\n --innerFloat: left;\n --outerFloat: right;\n\n --spacerHeights-start: 0px;\n --spacerHeights-outer: 0px;\n --spacerHeights-inner: 0px;\n --spacerHeights-end: 0px;\n\n /*Edge Cases*/\n --hasInnerStartGap: 0;\n --hasOuterStartGap: 0;\n --innerStartWidth: 50%;\n --innerPadding: 0px;\n --outerStartWidth: 50%;\n --outerPadding: 0px;\n}\n\n/*Containers*/\n.styles_dafRoot__1QUlM,\n.styles_outer__abXQX,\n.styles_inner__x-amJ,\n.styles_main__BHTRd {\n width: var(--contentWidth);\n pointer-events: none;\n box-sizing: content-box;\n}\n\n.styles_outer__abXQX, .styles_inner__x-amJ, .styles_main__BHTRd {\n position: absolute;\n}\n\n/*Float changes with amud*/\n.styles_inner__x-amJ .styles_spacer__2T7TS,\n.styles_main__BHTRd .styles_spacer__2T7TS.styles_outerMid__2WtcY {\n float: var(--innerFloat);\n}\n\n.styles_outer__abXQX .styles_spacer__2T7TS,\n.styles_main__BHTRd .styles_spacer__2T7TS.styles_innerMid__27MCi {\n float: var(--outerFloat);\n}\n\n/*Spacer widths determined by options*/\n.styles_inner__x-amJ .styles_spacer__2T7TS,\n.styles_outer__abXQX .styles_spacer__2T7TS {\n width: var(--halfway);\n}\n.styles_spacer__2T7TS.styles_mid__dcgUr {\n width: var(--remainderPercent);\n}\n\n.styles_main__BHTRd .styles_spacer__2T7TS.styles_start__AwkfY {\n width: var(--contentWidth);\n}\n.styles_main__BHTRd .styles_spacer__2T7TS.styles_innerMid__27MCi,\n.styles_main__BHTRd .styles_spacer__2T7TS.styles_outerMid__2WtcY {\n width: var(--sidePercent);\n}\n\n/*Spacer heights determined by algorithm*/\n.styles_spacer__2T7TS.styles_start__AwkfY {\n height: var(--spacerHeights-start);\n}\n\n.styles_spacer__2T7TS.styles_end__2wr6A {\n height: var(--spacerHeights-end);\n}\n\n.styles_inner__x-amJ .styles_spacer__2T7TS.styles_mid__dcgUr,\n.styles_main__BHTRd .styles_spacer__2T7TS.styles_innerMid__27MCi {\n height: var(--spacerHeights-inner);\n}\n.styles_outer__abXQX .styles_spacer__2T7TS.styles_mid__dcgUr,\n.styles_main__BHTRd .styles_spacer__2T7TS.styles_outerMid__2WtcY {\n height: var(--spacerHeights-outer);\n}\n\n/*Settings to handle edge Cases*/\n\n.styles_inner__x-amJ .styles_spacer__2T7TS.styles_start__AwkfY {\n width: var(--innerStartWidth);\n margin-left: var(--innerPadding);\n margin-right: var(--innerPadding);\n}\n\n.styles_outer__abXQX .styles_spacer__2T7TS.styles_start__AwkfY {\n width: var(--outerStartWidth);\n margin-left: var(--outerPadding);\n margin-right: var(--outerPadding);\n}\n\n.styles_inner__x-amJ .styles_spacer__2T7TS.styles_start__AwkfY{\n margin-bottom: calc(var(--padding-vertical) * var(--hasInnerStartGap));\n}\n.styles_outer__abXQX .styles_spacer__2T7TS.styles_start__AwkfY {\n margin-bottom: calc(var(--padding-vertical) * var(--hasOuterStartGap));\n}\n\n.styles_spacer__2T7TS.styles_mid__dcgUr {\n clear: both;\n}\n\n/*Margins!*/\n.styles_spacer__2T7TS.styles_start__AwkfY,\n.styles_spacer__2T7TS.styles_end__2wr6A,\n.styles_main__BHTRd .styles_spacer__2T7TS.styles_innerMid__27MCi,\n.styles_main__BHTRd .styles_spacer__2T7TS.styles_outerMid__2WtcY {\n margin-left: calc(0.5 * var(--padding-horizontal));\n margin-right: calc(0.5 * var(--padding-horizontal));\n}\n\n.styles_spacer__2T7TS.styles_mid__dcgUr,\n.styles_main__BHTRd .styles_text__1_7-z {\n margin-top: var(--padding-vertical);\n}\n\n.styles_spacer__2T7TS.styles_mid__dcgUr,\n.styles_main__BHTRd .styles_spacer__2T7TS.styles_innerMid__27MCi,\n.styles_main__BHTRd .styles_spacer__2T7TS.styles_outerMid__2WtcY {\n margin-bottom: var(--padding-vertical);\n}\n\n/*Text*/\n.styles_text__1_7-z {\n direction: var(--direction);\n text-align: justify;\n text-align-last: center;\n}\n\n.styles_text__1_7-z span {\n pointer-events: auto;\n}\n\n.styles_main__BHTRd .styles_text__1_7-z {\n font-family: var(--fontFamily-main);\n font-size: var(--fontSize-main);\n line-height: var(--lineHeight-main);\n}\n\n.styles_inner__x-amJ .styles_text__1_7-z,\n.styles_outer__abXQX .styles_text__1_7-z {\n font-size: var(--fontSize-side);\n line-height: var(--lineHeight-side);\n}\n\n.styles_inner__x-amJ .styles_text__1_7-z {\n font-family: var(--fontFamily-inner);\n}\n\n.styles_outer__abXQX .styles_text__1_7-z {\n font-family: var(--fontFamily-outer);\n}\n');const s={start:[a.spacer,a.start],mid:[a.spacer,a.mid],end:[a.spacer,a.end]},r={el:a.dafRoot,outer:{el:a.outer,spacers:s,text:a.text},inner:{el:a.inner,spacers:s,text:a.text},main:{el:a.main,spacers:{start:s.start,inner:[a.spacer,a.innerMid],outer:[a.spacer,a.outerMid]},text:a.text}};function o(e,t=""){const n=Array.prototype.find.call(document.styleSheets,(e=>e.rules[0].selectorText==`.${a.dafRoot}`)).rules[0];Object.entries(e).forEach((([e,i])=>{"string"==typeof i?n.style.setProperty(`--${t}${e}`,i):"object"==typeof i&&o(i,`${e}-`)}))}let l;var d={applyClasses(e,t=r){for(const a in e)if(a in t){const s=t[a];"object"!=typeof s||Array.isArray(s)?(n=e[a],i=s,Array.isArray(i)?n.classList.add(...i):n.classList.add(i)):this.applyClasses(e[a],s)}var n,i},updateOptionsVars(e){l=e,o(e)},updateSpacersVars(e){o(Object.fromEntries(Object.entries(e).map((([e,t])=>[e,String(t)+"px"]))),"spacerHeights-")},updateIsAmudB(e){o({innerFloat:e?"right":"left",outerFloat:e?"left":"right"})},manageExceptions(e){e.exception?1===e.exception?(console.log("In Style Exception, Case 1"),o({hasInnerStartGap:"1",innerStartWidth:"100%",outerStartWidth:"0%",innerPadding:"0px",outerPadding:"0px"})):2===e.exception&&(console.log("In Style Exception, Case 2"),o({hasOuterStartGap:"1",outerStartWidth:"100%",innerStartWidth:"0%",innerPadding:"0px",outerPadding:"0px"})):o({hasOuterStartGap:"0",hasInnerStartGap:"0",outerStartWidth:"50%",innerStartWidth:"50%",innerPadding:l.innerPadding,outerPadding:l.outerPadding})}};function _(e,t,n,i,a){a.innerHTML="";let s=document.createElement("span");s.style.font=n+" "+String(t),s.style.lineHeight=String(i)+"px",s.innerHTML=e,s.style.position="absolute",a.append(s);const r=s.getBoundingClientRect(),o=r.height,l=r.width,d=l/a.getBoundingClientRect().width;return s.remove(),{height:o,width:l,widthProportional:d}}function h(e,t,n,i){return a=>_(a.join("
"),e,t,n,i).height}function c(e){const t=e.map((e=>e.widthProportional)),n=t.map(((e,t,n)=>0==t?0:Math.abs(e-n[t-1])));let i=n.reduce(((e,t,n)=>{if(n<4)return e;if(t>.12){const t=e[e.length-1];if(t&&n-t==1)return e;e.push(n)}return e}),[]);var a;return i=(a=i,a.map(((e,n)=>{let i,a;i||(i=Math.min(e+3,t.length-1)),a||(a=Math.max(e-3,0));const s=t.slice(a,e).reduce(((e,t)=>e+t))/(e-a)/t[e];let r;return r=e+1>=i?t[i]/t[e]:t.slice(e+1,i).reduce(((e,t)=>e+t))/(i-e-1)/t[e],{point:e,before:s,after:r,diff:Math.abs(r-s)}}))).sort(((e,t)=>t.diff-e.diff)).filter((({diff:e})=>e>.22)).map((({point:e})=>e)),i.sort(((e,t)=>e-t))}function p(e,t,n,i,a){const s={main:e,rashi:t,tosafot:n},r={padding:{vertical:parseFloat(i.padding.vertical),horizontal:parseFloat(i.padding.horizontal)},halfway:.01*parseFloat(i.halfway),fontFamily:i.fontFamily,fontSize:{main:i.fontSize.main,side:i.fontSize.side},lineHeight:{main:parseFloat(i.lineHeight.main),side:parseFloat(i.lineHeight.side)}},o=[r.fontFamily.main,r.fontSize.main,r.lineHeight.main],l=[r.fontFamily.inner,r.fontSize.side,r.lineHeight.side],d={};d.main=s.main.map((e=>_(e,...o,a))),["rashi","tosafot"].forEach((e=>{d[e]=s[e].map((e=>_(e,...l,a)))}));const p=h(...o,a),m=h(...l,a),u={};["rashi","tosafot","main"].forEach((e=>{u[e]=c(d[e]).filter((t=>!s[e][t].includes("hadran")))}));const g={start:4.4*r.lineHeight.side,inner:null,outer:null,end:0,exception:0},y=p(s.main);d.main.length,r.lineHeight.main;let f={inner:m(s.rashi.slice(4)),outer:m(s.tosafot.slice(4))};r.lineHeight.side,d.rashi.length,r.lineHeight.side,d.tosafot.length;switch((u.rashi.length<1||u.tosafot.length<1)&&(console.log("Dealing with Exceptions"),u.rashi.length<1&&(f.inner=r.lineHeight.side*(d.rashi.length+1),g.exception=2),u.tosafot.length<1&&(f.outer=r.lineHeight.side*(d.tosafot.length+1),g.exception=2)),u.main.length){case 0:g.inner=y,g.outer=y,2==u.rashi.length?g.end=m(s.rashi.slice(u.rashi[1])):g.end=m(s.tosafot.slice(u.tosafot[1])),console.log("Double wrap");break;case 1:if(u.rashi.length!=u.tosafot.length){if(0==u.tosafot.length){g.outer=0,g.inner=f.inner;break}if(0==u.rashi.length){g.inner=0,g.outer=f.outer;break}let e,t;1==u.rashi.length?(e="outer",t="inner"):(e="inner",t="outer"),g[t]=f[t],g[e]=y,console.log("Stairs");break}case 2:g.inner=f.inner,g.outer=f.outer,console.log("Double Extend");break;default:g.inner=f.inner,g.outer=f.outer,console.log("No Case Exception")}return g}function m(e,t){const n=document.createElement(e);return t&&t.append(n),n}function u(e){return m("div",e)}function g(e){return m("span",e)}return function(n,s=e){const r="string"==typeof n?document.querySelector(n):n;if(!(r&&r instanceof Element&&"DIV"===r.tagName.toUpperCase()))throw"Argument must be a div element or its selector";const o=u(r),l=u(r),h=u(r),m=u(r);m.id="dummy";const y={el:r,dummy:m,outer:{el:o,spacers:{start:u(o),mid:u(o),end:u(o)},text:u(o)},inner:{el:l,spacers:{start:u(l),mid:u(l),end:u(l)},text:u(l)},main:{el:h,spacers:{start:u(h),inner:u(h),outer:u(h)},text:u(h)}},f={main:g(y.main.text),inner:g(y.inner.text),outer:g(y.outer.text)},x=t(s,e);let T;return d.applyClasses(y),d.updateOptionsVars(x),{classes:a,containers:y,spacerHeights:{start:0,inner:0,outer:0,end:0},amud:"a",render(e,t,n,a="a",s,r,o){if(T&&window.removeEventListener("resize",T),this.amud!=a&&(this.amud=a,d.updateIsAmudB("b"==a)),s){let[i,r,l]=[e,t,n].map((e=>{y.dummy.innerHTML=e;const t=Array.from(y.dummy.querySelectorAll("div")).map((e=>{const t=document.createRange();return t.selectNode(e),t})),n=y.dummy.querySelectorAll(s),i=[];return n.forEach(((e,a)=>{const s=document.createRange();if(s.setEndBefore(e),0==a)s.setStart(y.dummy,0);else{const e=n[a-1];s.setStartAfter(e)}t.forEach(((e,n)=>{s.compareBoundaryPoints(Range.START_TO_START,e)<0&&s.compareBoundaryPoints(Range.END_TO_END,e)>0&&(i.push(e.extractContents()),t.splice(n,1))})),i.push(s.extractContents())})),i.map((e=>{const t=document.createElement("div");return t.append(e),t.innerHTML}))}));y.dummy.innerHTML="";const h=0!=r.length;if(h!=(0!=l.length)){const e=function(e,t,n){const i=t.fontFamily.inner,a=t.fontSize.side,s=parseFloat(t.lineHeight.side),r=c(e.map((e=>_(e,i,a,s,n))));if(3==r.length)return[e.slice(0,r[1]),e.slice(r[1])]}(h?r:l,x,m);e&&("a"==a?(r=e[0],l=e[1]):(r=e[1],l=e[0]),t=r.join("
"),n=l.join("
"))}this.spacerHeights=p(i,r,l,x,y.dummy),T=()=>{this.spacerHeights=p(i,r,l,x,y.dummy),d.updateSpacersVars(this.spacerHeights),o&&o(),console.log("resizing")},window.addEventListener("resize",T)}else this.spacerHeights=i(e,t,n,x,y.dummy),T=()=>{this.spacerHeights=i(e,t,n,x,y.dummy),d.updateSpacersVars(this.spacerHeights),console.log("resizing"),o&&o()},window.addEventListener("resize",T);d.updateSpacersVars(this.spacerHeights),d.manageExceptions(this.spacerHeights),f.main.innerHTML=e,f.inner.innerHTML=t,f.outer.innerHTML=n;const l=Math.max(...["main","inner","outer"].map((e=>y[e].el.offsetHeight)));y.el.style.height=`${l}px`,r&&r()}}}}));
2 |
--------------------------------------------------------------------------------
/dist/daf-renderer.umd.js:
--------------------------------------------------------------------------------
1 | (function (global, factory) {
2 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
3 | typeof define === 'function' && define.amd ? define(factory) :
4 | (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.dafRenderer = factory());
5 | }(this, (function () { 'use strict';
6 |
7 | const defaultOptions = {
8 | contentWidth: "600px",
9 | mainWidth: "50%",
10 | padding: {
11 | vertical: "10px",
12 | horizontal: "16px",
13 | },
14 | innerPadding: "4px",
15 | outerPadding: "4px",
16 | halfway: "50%",
17 | fontFamily: {
18 | inner: "Rashi",
19 | outer: "Rashi",
20 | main: "Vilna"
21 | },
22 | direction: "rtl",
23 | fontSize: {
24 | main: "15px",
25 | side: "10.5px"
26 | },
27 | lineHeight: {
28 | main: "17px",
29 | side: "14px",
30 | }
31 | };
32 |
33 | function mergeAndClone (modified, definitional = defaultOptions) {
34 | const newOptions = {};
35 | for (const key in definitional) {
36 | if (key in modified) {
37 | const defType = typeof definitional[key];
38 | if (typeof modified[key] !== defType) {
39 | console.error(`Option ${key} must be of type ${defType}; ${typeof modified[key]} was passed.`);
40 | }
41 | if (defType == "object") {
42 | newOptions[key] = mergeAndClone(modified[key], definitional[key]);
43 | } else {
44 | newOptions[key] = modified[key];
45 | }
46 | } else {
47 | newOptions[key] = definitional[key];
48 | }
49 | }
50 | return newOptions;
51 | }
52 |
53 | function getAreaOfText(text, font, fs, width, lh, dummy) {
54 | let testDiv = document.createElement("div");
55 | testDiv.style.font = String(fs) + "px " + String(font);
56 | testDiv.style.width = String(width) + "px"; //You can remove this, but it may introduce unforseen problems
57 | testDiv.style.lineHeight = String(lh) + "px";
58 | testDiv.innerHTML = text;
59 | dummy.append(testDiv);
60 | let test_area = Number(testDiv.clientHeight * testDiv.clientWidth);
61 | testDiv.remove();
62 | return test_area;
63 | }
64 |
65 | function calculateSpacers(mainText, innerText, outerText, options, dummy) {
66 |
67 | const parsedOptions = {
68 | width: parseFloat(options.contentWidth),
69 | padding: {
70 | vertical: parseFloat(options.padding.vertical),
71 | horizontal: parseFloat(options.padding.horizontal)
72 | },
73 | halfway: 0.01 * parseFloat(options.halfway),
74 | fontFamily: options.fontFamily,
75 | fontSize: {
76 | main: parseFloat(options.fontSize.main),
77 | side: parseFloat(options.fontSize.side),
78 | },
79 | lineHeight: {
80 | main: parseFloat(options.lineHeight.main),
81 | side: parseFloat(options.lineHeight.side),
82 | },
83 | mainWidth: 0.01 * parseFloat(options.mainWidth)
84 | };
85 |
86 | const midWidth = Number(parsedOptions.width * parsedOptions.mainWidth) - 2*parsedOptions.padding.horizontal; //main middle strip
87 | const topWidth = Number(parsedOptions.width * parsedOptions.halfway) - parsedOptions.padding.horizontal; //each commentary top
88 | const sideWidth = Number(parsedOptions.width * (1 - parsedOptions.mainWidth)/2); //each commentary widths, dont include padding, sokeep it constant
89 |
90 | const spacerHeights = {
91 | start: 4.3 * parsedOptions.lineHeight.side,
92 | inner: null,
93 | outer: null,
94 | end: 0,
95 | exception: 0
96 | };
97 |
98 | // We are accounting for the special case, where you have line breaks:
99 | // if (options.lineBreaks) {
100 | // console.log("Special Case for Line Breaks")
101 | // const main = {
102 | // name: "main",
103 | // text: mainText,
104 | // lineHeight: parsedOptions.lineHeight.main,
105 | // top: 0,
106 | // }
107 | // const outer = {
108 | // name: "outer",
109 | // text: outerText,
110 | // lineHeight: parsedOptions.lineHeight.side,
111 | // top: 4,
112 | // }
113 | // const inner = {
114 | // name: "inner",
115 | // text: innerText,
116 | // lineHeight: parsedOptions.lineHeight.side,
117 | // top: 4,
118 | // }
119 | //
120 | // const texts = [main, outer, inner];
121 | // texts.forEach(body => body.brCount = (body.text.match(/
/g) || []).length - body.top);
122 | // texts.forEach(body => body.height = (body.brCount * body.lineHeight));
123 | // texts.forEach(body => body.unadjustedHeight = ((body.brCount + body.top + 1) * body.lineHeight));
124 | // texts.forEach(body => body.unadjustedHeightAlt = ((body.brCount + body.top) * body.lineHeight)*sideWidth/topWidth);
125 | // const perHeight = Array.from(texts).sort((a, b) => a.height - b.height);
126 | //
127 | // const exConst = 2.2
128 | //
129 | // //Checking Exceptions:
130 | // if (inner.unadjustedHeight <= 0 && outer.unadjustedHeight <= 0){
131 | // console.error("No Commentary");
132 | // return Error("No Commentary");
133 | // };
134 | // if (inner.unadjustedHeightAlt/exConst < spacerHeights.start || outer.unadjustedHeightAlt/exConst < spacerHeights.start) {
135 | // console.log("Exceptions")
136 | // if (inner.unadjustedHeightAlt/exConst <= spacerHeights.start) {
137 | // spacerHeights.inner = inner.unadjustedHeight;
138 | // spacerHeights.outer = outer.height
139 | // return spacerHeights;
140 | // }
141 | // if (outer.unadjustedHeightAlt/exConst <= spacerHeights.start) {
142 | // spacerHeights.outer = outer.unadjustedHeight;
143 | // spacerHeights.inner = inner.height;
144 | // return spacerHeights;
145 | // }
146 | // else {
147 | // return Error("Inner Spacer Error");
148 | // }
149 | // };
150 | // //If Double=Wrap
151 | // if (perHeight[0].name === "main"){
152 | // console.log("Double-Wrap");
153 | // spacerHeights.inner = main.height;
154 | // spacerHeights.outer = main.height;
155 | //
156 | // const brDifference = perHeight[1].brCount - perHeight[0].brCount;
157 | // spacerHeights.end = brDifference*perHeight[1].lineHeight;
158 | // return spacerHeights;
159 | // }
160 | //
161 | // //If Stairs
162 | // if (perHeight[1].name === "main") {
163 | // console.log("Stairs");
164 | // spacerHeights[perHeight[0].name] = perHeight[0].height;
165 | // spacerHeights[perHeight[2].name] = main.height;
166 | // return spacerHeights;
167 | // }
168 | //
169 | // //If Double Extend
170 | // console.log("Double-Extend")
171 | // spacerHeights.inner = inner.height + (inner.height/inner.height**2)*inner.lineHeight;
172 | // spacerHeights.outer = outer.height + (inner.height/inner.height**2)*outer.lineHeight;
173 | // return spacerHeights
174 | // }
175 |
176 |
177 | // We could probably put this somewhere else, it was meant to be a place for all the padding corrections,
178 | // but there turned out to only be one
179 | const paddingAreas = {
180 | name: "paddingAreas",
181 | horizontalSide: sideWidth * parsedOptions.padding.vertical,
182 | };
183 |
184 |
185 | const topArea = (lineHeight) => ((4 * lineHeight * topWidth)); //remove area of the top 4 lines
186 |
187 |
188 | const main = {
189 | name: "main",
190 | width: midWidth,
191 | text: mainText,
192 | lineHeight: parsedOptions.lineHeight.main,
193 | area: getAreaOfText(mainText, parsedOptions.fontFamily.main, parsedOptions.fontSize.main, midWidth, parsedOptions.lineHeight.main, dummy),
194 | length: null,
195 | height: null,
196 | };
197 | const outer = {
198 | name: "outer",
199 | width: sideWidth,
200 | text: outerText,
201 | lineHeight: parsedOptions.lineHeight.side,
202 | area: getAreaOfText(outerText, parsedOptions.fontFamily.outer, parsedOptions.fontSize.side, sideWidth, parsedOptions.lineHeight.side, dummy)
203 | - topArea(parsedOptions.lineHeight.side),
204 | length: null,
205 | height: null,
206 | };
207 | const inner = {
208 | name: "inner",
209 | width: sideWidth,
210 | text: innerText,
211 | lineHeight: parsedOptions.lineHeight.side,
212 | area:
213 | getAreaOfText(innerText, parsedOptions.fontFamily.inner, parsedOptions.fontSize.side, sideWidth, parsedOptions.lineHeight.side, dummy)
214 | - topArea(parsedOptions.lineHeight.side),
215 | length: null,
216 | height: null,
217 | };
218 |
219 | const texts = [main, outer, inner];
220 | texts.forEach(text => text.height = text.area / text.width);
221 | texts.forEach(text => text.unadjustedArea = text.area + topArea(parsedOptions.lineHeight.side));
222 | texts.forEach(text => text.unadjustedHeight = text.unadjustedArea / text.width);
223 |
224 | const perHeight = Array.from(texts).sort((a, b) => a.height - b.height);
225 |
226 | //There are Three Main Types of Case:
227 | //Double-Wrap: The main text being the smallest and commentaries wrapping around it
228 | //Stairs: The main text wrapping around one, but the other wrapping around it
229 | //Double-Extend: The main text wrapping around both commentaries
230 |
231 | //Main Text is Smallest: Double-Wrap
232 | //Main Text being Middle: Stairs
233 | //Main Text Being Largest: Double-Extend
234 |
235 | //First we need to check we have enough commentary to fill the first four lines
236 | if (inner.height <= 0 && outer.height <= 0){
237 | console.error("No Commentary");
238 | return Error("No Commentary");
239 | }
240 |
241 | // This is a case that we have to decice what to do with, when there is not enough commentary on both sides to fill the lines.
242 | if (inner.height <= spacerHeights.start && outer.height <= spacerHeights.start) {
243 | console.error("Not Enough Commentary to Fill Four Lines");
244 | return Error("Not Enough Commentary");
245 | }
246 | // We are going to deal with our first edge case when there is either only one commentary
247 | // Or where there is enough of one commentary, but not four lines of the other.
248 | if (inner.unadjustedHeight <= spacerHeights.start || outer.unadjustedHeight <= spacerHeights.start) {
249 | if (inner.unadjustedHeight <= spacerHeights.start) {
250 | spacerHeights.inner = inner.unadjustedHeight;
251 | spacerHeights.outer = (outer.unadjustedArea - parsedOptions.width * 4 * parsedOptions.lineHeight.side) / sideWidth;
252 | spacerHeights.exception = 1;
253 | return spacerHeights;
254 | }
255 | if (outer.unadjustedHeight <= spacerHeights.start) {
256 | spacerHeights.outer = outer.unadjustedHeight;
257 |
258 | spacerHeights.inner = (inner.unadjustedArea - parsedOptions.width * 4 * parsedOptions.lineHeight.side) / sideWidth;
259 | spacerHeights.exception = 2;
260 | return spacerHeights;
261 | }
262 | else {
263 | return Error("Inner Spacer Error");
264 | }
265 | }
266 | //If Double=Wrap
267 | if (perHeight[0].name === "main"){
268 | console.log("Double-Wrap");
269 | spacerHeights.inner = main.area/midWidth;
270 | spacerHeights.outer = spacerHeights.inner;
271 |
272 | const sideArea = spacerHeights.inner * sideWidth + paddingAreas.horizontalSide;
273 | const bottomChunk = perHeight[1].area - sideArea;
274 | const bottomHeight = bottomChunk / topWidth;
275 | spacerHeights.end = bottomHeight;
276 | return spacerHeights;
277 | }
278 | // If Stairs, there's one text at the bottom. We will call it THE stair.
279 | // The remaining two texts form a "block" that we must compare with that bottom text.
280 | const blockArea = (main.area + perHeight[0].area);
281 | const blockWidth = midWidth + sideWidth;
282 | const blockHeight = blockArea / blockWidth;
283 |
284 | const stair = (perHeight[1].name == "main") ? perHeight[2] : perHeight[1];
285 | const stairHeight = stair.area / stair.width;
286 |
287 | if (blockHeight < stairHeight) {
288 | console.log(`Stairs, ${stair.name} is the stair`);
289 | // This function accounts for extra space that is introduced by padding
290 | const lilArea = (height1, height2, horizPadding) => (horizPadding) * (height1 - height2);
291 | const smallest = perHeight[0];
292 | spacerHeights[smallest.name] = smallest.height;
293 | spacerHeights[stair.name] = (blockArea - lilArea(blockHeight, spacerHeights[smallest.name], parsedOptions.padding.horizontal)) / blockWidth;
294 | return spacerHeights
295 | }
296 | //If Double Extend
297 | console.log("Double-Extend");
298 | spacerHeights.inner = inner.height;
299 | spacerHeights.outer = outer.height;
300 |
301 | return spacerHeights
302 | }
303 |
304 | function styleInject(css, ref) {
305 | if ( ref === void 0 ) ref = {};
306 | var insertAt = ref.insertAt;
307 |
308 | if (!css || typeof document === 'undefined') { return; }
309 |
310 | var head = document.head || document.getElementsByTagName('head')[0];
311 | var style = document.createElement('style');
312 | style.type = 'text/css';
313 |
314 | if (insertAt === 'top') {
315 | if (head.firstChild) {
316 | head.insertBefore(style, head.firstChild);
317 | } else {
318 | head.appendChild(style);
319 | }
320 | } else {
321 | head.appendChild(style);
322 | }
323 |
324 | if (style.styleSheet) {
325 | style.styleSheet.cssText = css;
326 | } else {
327 | style.appendChild(document.createTextNode(css));
328 | }
329 | }
330 |
331 | var css_248z = "/*Keep this as the first rule in the file*/\n.styles_dafRoot__1QUlM {\n --contentWidth: 0px;\n --padding-horizontal: 0px;\n --padding-vertical: 0px;\n --halfway: 50%;\n\n --fontFamily-inner: \"Rashi\";\n --fontFamily-outer: \"Tosafot\";\n --fontFamily-main: \"Vilna\";\n --direction: \"rtl\";\n\n --fontSize-main: 0px;\n --fontSize-side: 0px;\n\n --lineHeight-main: 0px;\n --lineHeight-side: 0px;\n\n --mainWidth: 0%;\n --mainMargin-start: var(--mainWidth);\n --sidePercent: calc(calc(100% - var(--mainMargin-start)) / 2);\n --remainderPercent: calc(100% - var(--sidePercent));\n\n --innerFloat: left;\n --outerFloat: right;\n\n --spacerHeights-start: 0px;\n --spacerHeights-outer: 0px;\n --spacerHeights-inner: 0px;\n --spacerHeights-end: 0px;\n\n /*Edge Cases*/\n --hasInnerStartGap: 0;\n --hasOuterStartGap: 0;\n --innerStartWidth: 50%;\n --innerPadding: 0px;\n --outerStartWidth: 50%;\n --outerPadding: 0px;\n}\n\n/*Containers*/\n.styles_dafRoot__1QUlM,\n.styles_outer__abXQX,\n.styles_inner__x-amJ,\n.styles_main__BHTRd {\n width: var(--contentWidth);\n pointer-events: none;\n box-sizing: content-box;\n}\n\n.styles_outer__abXQX, .styles_inner__x-amJ, .styles_main__BHTRd {\n position: absolute;\n}\n\n/*Float changes with amud*/\n.styles_inner__x-amJ .styles_spacer__2T7TS,\n.styles_main__BHTRd .styles_spacer__2T7TS.styles_outerMid__2WtcY {\n float: var(--innerFloat);\n}\n\n.styles_outer__abXQX .styles_spacer__2T7TS,\n.styles_main__BHTRd .styles_spacer__2T7TS.styles_innerMid__27MCi {\n float: var(--outerFloat);\n}\n\n/*Spacer widths determined by options*/\n.styles_inner__x-amJ .styles_spacer__2T7TS,\n.styles_outer__abXQX .styles_spacer__2T7TS {\n width: var(--halfway);\n}\n.styles_spacer__2T7TS.styles_mid__dcgUr {\n width: var(--remainderPercent);\n}\n\n.styles_main__BHTRd .styles_spacer__2T7TS.styles_start__AwkfY {\n width: var(--contentWidth);\n}\n.styles_main__BHTRd .styles_spacer__2T7TS.styles_innerMid__27MCi,\n.styles_main__BHTRd .styles_spacer__2T7TS.styles_outerMid__2WtcY {\n width: var(--sidePercent);\n}\n\n/*Spacer heights determined by algorithm*/\n.styles_spacer__2T7TS.styles_start__AwkfY {\n height: var(--spacerHeights-start);\n}\n\n.styles_spacer__2T7TS.styles_end__2wr6A {\n height: var(--spacerHeights-end);\n}\n\n.styles_inner__x-amJ .styles_spacer__2T7TS.styles_mid__dcgUr,\n.styles_main__BHTRd .styles_spacer__2T7TS.styles_innerMid__27MCi {\n height: var(--spacerHeights-inner);\n}\n.styles_outer__abXQX .styles_spacer__2T7TS.styles_mid__dcgUr,\n.styles_main__BHTRd .styles_spacer__2T7TS.styles_outerMid__2WtcY {\n height: var(--spacerHeights-outer);\n}\n\n/*Settings to handle edge Cases*/\n\n.styles_inner__x-amJ .styles_spacer__2T7TS.styles_start__AwkfY {\n width: var(--innerStartWidth);\n margin-left: var(--innerPadding);\n margin-right: var(--innerPadding);\n}\n\n.styles_outer__abXQX .styles_spacer__2T7TS.styles_start__AwkfY {\n width: var(--outerStartWidth);\n margin-left: var(--outerPadding);\n margin-right: var(--outerPadding);\n}\n\n.styles_inner__x-amJ .styles_spacer__2T7TS.styles_start__AwkfY{\n margin-bottom: calc(var(--padding-vertical) * var(--hasInnerStartGap));\n}\n.styles_outer__abXQX .styles_spacer__2T7TS.styles_start__AwkfY {\n margin-bottom: calc(var(--padding-vertical) * var(--hasOuterStartGap));\n}\n\n.styles_spacer__2T7TS.styles_mid__dcgUr {\n clear: both;\n}\n\n/*Margins!*/\n.styles_spacer__2T7TS.styles_start__AwkfY,\n.styles_spacer__2T7TS.styles_end__2wr6A,\n.styles_main__BHTRd .styles_spacer__2T7TS.styles_innerMid__27MCi,\n.styles_main__BHTRd .styles_spacer__2T7TS.styles_outerMid__2WtcY {\n margin-left: calc(0.5 * var(--padding-horizontal));\n margin-right: calc(0.5 * var(--padding-horizontal));\n}\n\n.styles_spacer__2T7TS.styles_mid__dcgUr,\n.styles_main__BHTRd .styles_text__1_7-z {\n margin-top: var(--padding-vertical);\n}\n\n.styles_spacer__2T7TS.styles_mid__dcgUr,\n.styles_main__BHTRd .styles_spacer__2T7TS.styles_innerMid__27MCi,\n.styles_main__BHTRd .styles_spacer__2T7TS.styles_outerMid__2WtcY {\n margin-bottom: var(--padding-vertical);\n}\n\n/*Text*/\n.styles_text__1_7-z {\n direction: var(--direction);\n text-align: justify;\n text-align-last: center;\n}\n\n.styles_text__1_7-z span {\n pointer-events: auto;\n}\n\n.styles_main__BHTRd .styles_text__1_7-z {\n font-family: var(--fontFamily-main);\n font-size: var(--fontSize-main);\n line-height: var(--lineHeight-main);\n}\n\n.styles_inner__x-amJ .styles_text__1_7-z,\n.styles_outer__abXQX .styles_text__1_7-z {\n font-size: var(--fontSize-side);\n line-height: var(--lineHeight-side);\n}\n\n.styles_inner__x-amJ .styles_text__1_7-z {\n font-family: var(--fontFamily-inner);\n}\n\n.styles_outer__abXQX .styles_text__1_7-z {\n font-family: var(--fontFamily-outer);\n}\n";
332 | var classes = {"dafRoot":"styles_dafRoot__1QUlM","outer":"styles_outer__abXQX","inner":"styles_inner__x-amJ","main":"styles_main__BHTRd","spacer":"styles_spacer__2T7TS","outerMid":"styles_outerMid__2WtcY","innerMid":"styles_innerMid__27MCi","mid":"styles_mid__dcgUr","start":"styles_start__AwkfY","end":"styles_end__2wr6A","text":"styles_text__1_7-z"};
333 | styleInject(css_248z);
334 |
335 | const sideSpacersClasses = {
336 | start: [classes.spacer, classes.start],
337 | mid: [classes.spacer, classes.mid],
338 | end: [classes.spacer, classes.end]
339 | };
340 |
341 | const containerClasses = {
342 | el: classes.dafRoot,
343 | outer: {
344 | el: classes.outer,
345 | spacers: sideSpacersClasses,
346 | text: classes.text,
347 | },
348 | inner: {
349 | el: classes.inner,
350 | spacers: sideSpacersClasses,
351 | text: classes.text,
352 | },
353 | main: {
354 | el: classes.main,
355 | spacers: {
356 | start: sideSpacersClasses.start,
357 | inner: [classes.spacer, classes.innerMid],
358 | outer: [classes.spacer, classes.outerMid]
359 | },
360 | text: classes.text
361 | }
362 | };
363 |
364 | function addClasses(element, classNames) {
365 | if (Array.isArray(classNames))
366 | element.classList.add(...classNames);
367 | else
368 | element.classList.add(classNames);
369 | }
370 |
371 | function setVars(object, prefix = "") {
372 | const varsRule = Array.prototype.find.call
373 | (document.styleSheets, sheet => sheet.rules[0].selectorText == `.${classes.dafRoot}`)
374 | .rules[0];
375 |
376 | Object.entries(object).forEach(([key, value]) => {
377 | if (typeof value == "string") {
378 | varsRule.style.setProperty(`--${prefix}${key}`, value);
379 | } else if (typeof value == "object") {
380 | setVars(value, `${key}-`);
381 | }
382 | });
383 | }
384 |
385 |
386 | let appliedOptions;
387 | var styleManager = {
388 | applyClasses(containers, classesMap = containerClasses) {
389 | for (const key in containers) {
390 | if (key in classesMap) {
391 | const value = classesMap[key];
392 | if (typeof value === "object" && !Array.isArray(value)) {
393 | this.applyClasses(containers[key], value);
394 | } else {
395 | addClasses(containers[key], value);
396 | }
397 | }
398 | }
399 | },
400 | updateOptionsVars(options) {
401 | appliedOptions = options;
402 | setVars(options);
403 | },
404 | updateSpacersVars(spacerHeights) {
405 | setVars(
406 | Object.fromEntries(
407 | Object.entries(spacerHeights).map(
408 | ([key, value]) => ([key, String(value) + 'px']))
409 | ),
410 | "spacerHeights-"
411 | );
412 | },
413 | updateIsAmudB(amudB) {
414 | setVars({
415 | innerFloat: amudB ? "right" : "left",
416 | outerFloat: amudB ? "left" : "right"
417 | });
418 | },
419 | manageExceptions(spacerHeights) {
420 | if (!spacerHeights.exception) {
421 | setVars({
422 | hasOuterStartGap: "0",
423 | hasInnerStartGap: "0",
424 | outerStartWidth: "50%",
425 | innerStartWidth: "50%",
426 | innerPadding: appliedOptions.innerPadding,
427 | outerPadding: appliedOptions.outerPadding,
428 | });
429 | return;
430 | }
431 | if (spacerHeights.exception === 1) {
432 | console.log("In Style Exception, Case 1");
433 | setVars({
434 | hasInnerStartGap: "1",
435 | innerStartWidth: "100%",
436 | outerStartWidth: "0%",
437 | innerPadding: "0px",
438 | outerPadding: "0px",
439 | });
440 | } else if (spacerHeights.exception === 2) {
441 | console.log("In Style Exception, Case 2");
442 | setVars({
443 | hasOuterStartGap: "1",
444 | outerStartWidth: "100%",
445 | innerStartWidth: "0%",
446 | innerPadding: "0px",
447 | outerPadding: "0px"
448 | });
449 | }
450 | }
451 | };
452 |
453 | function getLineInfo(text, font, fontSize, lineHeight, dummy) {
454 | dummy.innerHTML = "";
455 | let testDiv = document.createElement("span");
456 | testDiv.style.font = fontSize + " " + String(font);
457 | testDiv.style.lineHeight = String(lineHeight) + "px";
458 | testDiv.innerHTML = text;
459 | testDiv.style.position = "absolute";
460 | dummy.append(testDiv);
461 | const rect = testDiv.getBoundingClientRect();
462 | const height = rect.height;
463 | const width = rect.width;
464 | const widthProportional = width / dummy.getBoundingClientRect().width;
465 | testDiv.remove();
466 | return {height, width, widthProportional};
467 | }
468 |
469 | function heightAccumulator(font, fontSize, lineHeight, dummy) {
470 | return (lines) => {
471 | return getLineInfo(lines.join("
"), font, fontSize, lineHeight, dummy).height;
472 | }
473 | }
474 |
475 | function getBreaks(sizeArray) {
476 | const widths = sizeArray.map(size => size.widthProportional);
477 | const diffs = widths.map((width, index, widths) => index == 0 ? 0 : Math.abs(width - widths[index - 1]));
478 | const threshold = 0.12;
479 | let criticalPoints = diffs.reduce((indices, curr, currIndex) => {
480 | //Breaks before line 4 are flukes
481 | if (currIndex < 4) return indices;
482 | if (curr > threshold) {
483 | //There should never be two breakpoints in a row
484 | const prevIndex = indices[indices.length - 1];
485 | if (prevIndex && (currIndex - prevIndex) == 1) {
486 | return indices;
487 | }
488 | indices.push(currIndex);
489 | }
490 | return indices;
491 | }, []);
492 | const averageAround = points => points.map((point, i) => {
493 | let nextPoint;
494 | if (!nextPoint) {
495 | nextPoint = Math.min(point + 3, widths.length - 1);
496 | }
497 | let prevPoint;
498 | if (!prevPoint) {
499 | prevPoint = Math.max(point - 3, 0);
500 | }
501 | /*
502 | Note that these are divided by the width of the critical point line such that
503 | we get the average width of the preceeding and proceeding chunks *relative*
504 | to the critical line.
505 | */
506 | const before = (widths.slice(prevPoint, point).reduce((acc, curr) => acc + curr) /
507 | (point - prevPoint)) / widths[point];
508 | let after;
509 | if ( point + 1 >= nextPoint) {
510 | after = widths[nextPoint] / widths[point];
511 | } else {
512 | after =(widths.slice(point + 1, nextPoint).reduce((acc, curr) => acc + curr) /
513 | (nextPoint - point - 1)) / widths[point];
514 | }
515 | return {
516 | point,
517 | before,
518 | after,
519 | diff: Math.abs(after - before)
520 | }
521 | });
522 | const aroundDiffs = averageAround(criticalPoints)
523 | .sort( (a,b) => b.diff - a.diff);
524 | criticalPoints = aroundDiffs
525 | .filter( ({diff}) => diff > 0.22)
526 | .map( ({point}) => point);
527 | return criticalPoints.sort( (a, b) => a - b);
528 | }
529 |
530 | function onlyOneCommentary(lines, options, dummy) {
531 | const fontFamily = options.fontFamily.inner;
532 | const fontSize = options.fontSize.side;
533 | const lineHeight = parseFloat(options.lineHeight.side);
534 | const sizes = lines.map(text => getLineInfo(text, fontFamily, fontSize, lineHeight, dummy));
535 | const breaks = getBreaks(sizes);
536 | if (breaks.length == 3) {
537 | const first = lines.slice(0, breaks[1]);
538 | const second = lines.slice(breaks[1]);
539 | return [first, second];
540 | }
541 | }
542 |
543 | function calculateSpacersBreaks(mainArray, rashiArray, tosafotArray, options, dummy) {
544 | const lines = {
545 | main: mainArray,
546 | rashi: rashiArray,
547 | tosafot: tosafotArray
548 | };
549 |
550 | const parsedOptions = {
551 | padding: {
552 | vertical: parseFloat(options.padding.vertical),
553 | horizontal: parseFloat(options.padding.horizontal)
554 | },
555 | halfway: 0.01 * parseFloat(options.halfway),
556 | fontFamily: options.fontFamily, // Object of strings
557 | fontSize: {
558 | main: options.fontSize.main,
559 | side: options.fontSize.side,
560 | },
561 | lineHeight: {
562 | main: parseFloat(options.lineHeight.main),
563 | side: parseFloat(options.lineHeight.side),
564 | },
565 | };
566 |
567 |
568 | const mainOptions = [parsedOptions.fontFamily.main, parsedOptions.fontSize.main, parsedOptions.lineHeight.main];
569 | const commentaryOptions = [parsedOptions.fontFamily.inner, parsedOptions.fontSize.side, parsedOptions.lineHeight.side];
570 |
571 | const sizes = {};
572 | sizes.main = lines.main.map(text => getLineInfo(text, ...mainOptions, dummy));
573 | ["rashi", "tosafot"].forEach(text => {
574 | sizes[text] = lines[text].map(line => getLineInfo(line, ...commentaryOptions, dummy));
575 | });
576 |
577 | const accumulateMain = heightAccumulator(...mainOptions, dummy);
578 | const accumulateCommentary = heightAccumulator(...commentaryOptions, dummy);
579 |
580 | const breaks = {};
581 |
582 | ["rashi", "tosafot", "main"].forEach(text => {
583 | breaks[text] = getBreaks(sizes[text])
584 | /*
585 | Hadran lines aren't real candidates for line breaks.
586 | TODO: Extract this behavior , give it an option/parameter
587 | */
588 | .filter(lineNum => !(lines[text][lineNum].includes("hadran"))
589 | );
590 | });
591 |
592 |
593 | const spacerHeights = {
594 | start: 4.4 * parsedOptions.lineHeight.side,
595 | inner: null,
596 | outer: null,
597 | end: 0,
598 | exception: 0
599 | };
600 |
601 | const mainHeight = accumulateMain(lines.main);
602 | const mainHeightOld = (sizes.main.length) * parsedOptions.lineHeight.main;
603 | let afterBreak = {
604 | inner: accumulateCommentary(lines.rashi.slice(4)),
605 | outer: accumulateCommentary(lines.tosafot.slice(4))
606 | };
607 |
608 | let afterBreakOld = {
609 | inner: parsedOptions.lineHeight.side * (sizes.rashi.length - 4),
610 | outer: parsedOptions.lineHeight.side * (sizes.tosafot.length - 4)
611 | };
612 |
613 | if (breaks.rashi.length < 1 || breaks.tosafot.length < 1) {
614 | console.log("Dealing with Exceptions");
615 | if (breaks.rashi.length < 1) {
616 | afterBreak.inner = parsedOptions.lineHeight.side * (sizes.rashi.length + 1);
617 | spacerHeights.exception = 2;
618 | }
619 | if (breaks.tosafot.length < 1) {
620 | afterBreak.outer = parsedOptions.lineHeight.side * (sizes.tosafot.length + 1);
621 | spacerHeights.exception = 2;
622 | }
623 | }
624 | switch (breaks.main.length) {
625 | case 0:
626 | spacerHeights.inner = mainHeight;
627 | spacerHeights.outer = mainHeight;
628 | if (breaks.rashi.length == 2) {
629 | spacerHeights.end = accumulateCommentary(lines.rashi.slice(breaks.rashi[1]));
630 | } else {
631 | spacerHeights.end = accumulateCommentary(lines.tosafot.slice(breaks.tosafot[1]));
632 | }
633 | console.log("Double wrap");
634 | break;
635 | case 1:
636 | if (breaks.rashi.length != breaks.tosafot.length) {
637 | if (breaks.tosafot.length == 0) {
638 | spacerHeights.outer = 0;
639 | spacerHeights.inner = afterBreak.inner;
640 | break;
641 | }
642 | if (breaks.rashi.length == 0) {
643 | spacerHeights.inner = 0;
644 | spacerHeights.outer = afterBreak.outer;
645 | break;
646 | }
647 | let stair;
648 | let nonstair;
649 | if (breaks.rashi.length == 1) {
650 | stair = "outer";
651 | nonstair = "inner";
652 | } else {
653 | stair = "inner";
654 | nonstair = "outer";
655 | }
656 | spacerHeights[nonstair] = afterBreak[nonstair];
657 | spacerHeights[stair] = mainHeight;
658 | console.log("Stairs");
659 | break;
660 | }
661 | case 2:
662 | spacerHeights.inner = afterBreak.inner;
663 | spacerHeights.outer = afterBreak.outer;
664 | console.log("Double Extend");
665 | break;
666 | default:
667 | spacerHeights.inner = afterBreak.inner;
668 | spacerHeights.outer = afterBreak.outer;
669 | console.log("No Case Exception");
670 | break;
671 | }
672 | return spacerHeights;
673 | }
674 |
675 | function el(tag, parent) {
676 | const newEl = document.createElement(tag);
677 | if (parent) parent.append(newEl);
678 | return newEl;
679 | }
680 |
681 | function div(parent) {
682 | return el("div", parent);
683 | }
684 |
685 | function span(parent) {
686 | return el("span", parent);
687 | }
688 |
689 |
690 | function renderer (el, options = defaultOptions) {
691 | const root = (typeof el === "string") ? document.querySelector(el) : el;
692 | if (!(root && root instanceof Element && root.tagName.toUpperCase() === "DIV")) {
693 | throw "Argument must be a div element or its selector"
694 | }
695 | const outerContainer = div(root);
696 | const innerContainer = div(root);
697 | const mainContainer = div(root);
698 | const dummy = div(root);
699 | dummy.id = "dummy";
700 | const containers = {
701 | el: root,
702 | dummy: dummy,
703 | outer: {
704 | el: outerContainer,
705 | spacers: {
706 | start: div(outerContainer),
707 | mid: div(outerContainer),
708 | end: div(outerContainer)
709 | },
710 | text: div(outerContainer)
711 | },
712 | inner: {
713 | el: innerContainer,
714 | spacers: {
715 | start: div(innerContainer),
716 | mid: div(innerContainer),
717 | end: div(innerContainer)
718 | },
719 | text: div(innerContainer)
720 | },
721 | main: {
722 | el: mainContainer,
723 | spacers: {
724 | start: div(mainContainer),
725 | inner: div(mainContainer),
726 | outer: div(mainContainer),
727 | },
728 | text: div(mainContainer)
729 | }
730 | };
731 |
732 | const textSpans = {
733 | main: span(containers.main.text),
734 | inner: span(containers.inner.text),
735 | outer: span(containers.outer.text)
736 | };
737 |
738 | const clonedOptions = mergeAndClone(options, defaultOptions);
739 |
740 | styleManager.applyClasses(containers);
741 | styleManager.updateOptionsVars(clonedOptions);
742 |
743 | let resizeEvent;
744 | return {
745 | classes,
746 | containers,
747 | spacerHeights: {
748 | start: 0,
749 | inner: 0,
750 | outer: 0,
751 | end: 0
752 | },
753 | amud: "a",
754 | render(main, inner, outer, amud = "a", linebreak, renderCallback, resizeCallback) {
755 | if (resizeEvent) {
756 | window.removeEventListener("resize", resizeEvent);
757 | }
758 | if (this.amud != amud) {
759 | this.amud = amud;
760 | styleManager.updateIsAmudB(amud == "b");
761 | }
762 | if (!linebreak) {
763 | this.spacerHeights = calculateSpacers(main, inner, outer, clonedOptions, containers.dummy);
764 | resizeEvent = () => {
765 | this.spacerHeights = calculateSpacers(main, inner, outer, clonedOptions, containers.dummy);
766 | styleManager.updateSpacersVars(this.spacerHeights);
767 | console.log("resizing");
768 | if (resizeCallback)
769 | resizeCallback();
770 | };
771 | window.addEventListener("resize", resizeEvent);
772 | }
773 | else {
774 | let [mainSplit, innerSplit, outerSplit] = [main, inner, outer].map( text => {
775 | containers.dummy.innerHTML = text;
776 | const divRanges = Array.from(containers.dummy.querySelectorAll("div")).map(div => {
777 | const range = document.createRange();
778 | range.selectNode(div);
779 | return range;
780 | });
781 |
782 | const brs = containers.dummy.querySelectorAll(linebreak);
783 | const splitFragments = [];
784 | brs.forEach((node, index) => {
785 | const range = document.createRange();
786 | range.setEndBefore(node);
787 | if (index == 0) {
788 | range.setStart(containers.dummy, 0);
789 | } else {
790 | const prev = brs[index - 1];
791 | range.setStartAfter(prev);
792 | }
793 | divRanges.forEach( (divRange, i) => {
794 | const inBetween = range.compareBoundaryPoints(Range.START_TO_START, divRange) < 0 && range.compareBoundaryPoints(Range.END_TO_END, divRange) > 0;
795 | if (inBetween) {
796 | splitFragments.push(divRange.extractContents());
797 | divRanges.splice(i, 1);
798 | }
799 | });
800 |
801 | splitFragments.push(range.extractContents());
802 | });
803 |
804 | return splitFragments.map(fragment => {
805 | const el = document.createElement("div");
806 | el.append(fragment);
807 | return el.innerHTML;
808 | })
809 | });
810 |
811 | containers.dummy.innerHTML = "";
812 |
813 | const hasInner = innerSplit.length != 0;
814 | const hasOuter = outerSplit.length != 0;
815 |
816 | if (hasInner != hasOuter) {
817 | const withText = hasInner ? innerSplit : outerSplit;
818 | const fixed = onlyOneCommentary(withText, clonedOptions, dummy);
819 | if (fixed) {
820 | if (amud == "a") {
821 | innerSplit = fixed[0];
822 | outerSplit = fixed[1];
823 | } else {
824 | innerSplit = fixed[1];
825 | outerSplit = fixed[0];
826 | }
827 | inner = innerSplit.join('
');
828 | outer = outerSplit.join('
');
829 | }
830 | }
831 |
832 | this.spacerHeights = calculateSpacersBreaks(mainSplit, innerSplit, outerSplit, clonedOptions, containers.dummy);
833 | resizeEvent = () => {
834 | this.spacerHeights = calculateSpacersBreaks(mainSplit, innerSplit, outerSplit, clonedOptions, containers.dummy);
835 | styleManager.updateSpacersVars(this.spacerHeights);
836 | if (resizeCallback)
837 | resizeCallback();
838 | console.log("resizing");
839 | };
840 | window.addEventListener('resize', resizeEvent);
841 | }
842 | styleManager.updateSpacersVars(this.spacerHeights);
843 | styleManager.manageExceptions(this.spacerHeights);
844 | textSpans.main.innerHTML = main;
845 | textSpans.inner.innerHTML = inner;
846 | textSpans.outer.innerHTML = outer;
847 |
848 | const containerHeight = Math.max(...["main", "inner", "outer"].map(t => containers[t].el.offsetHeight));
849 | containers.el.style.height = `${containerHeight}px`;
850 | if (renderCallback)
851 | renderCallback();
852 | },
853 | }
854 | }
855 |
856 | return renderer;
857 |
858 | })));
859 |
--------------------------------------------------------------------------------
/doc-pictures/Adding Areas.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TalmudLab/daf-renderer/b2818adfd245e4a4857ad941d1d76e6e2eb42c62/doc-pictures/Adding Areas.PNG
--------------------------------------------------------------------------------
/doc-pictures/Area Labels.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TalmudLab/daf-renderer/b2818adfd245e4a4857ad941d1d76e6e2eb42c62/doc-pictures/Area Labels.PNG
--------------------------------------------------------------------------------
/doc-pictures/Comparing Areas.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TalmudLab/daf-renderer/b2818adfd245e4a4857ad941d1d76e6e2eb42c62/doc-pictures/Comparing Areas.PNG
--------------------------------------------------------------------------------
/doc-pictures/Double-Extend Case.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TalmudLab/daf-renderer/b2818adfd245e4a4857ad941d1d76e6e2eb42c62/doc-pictures/Double-Extend Case.PNG
--------------------------------------------------------------------------------
/doc-pictures/Double-Wrap Case.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TalmudLab/daf-renderer/b2818adfd245e4a4857ad941d1d76e6e2eb42c62/doc-pictures/Double-Wrap Case.PNG
--------------------------------------------------------------------------------
/doc-pictures/Spacer Labels.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TalmudLab/daf-renderer/b2818adfd245e4a4857ad941d1d76e6e2eb42c62/doc-pictures/Spacer Labels.PNG
--------------------------------------------------------------------------------
/doc-pictures/Spacers Together.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TalmudLab/daf-renderer/b2818adfd245e4a4857ad941d1d76e6e2eb42c62/doc-pictures/Spacers Together.PNG
--------------------------------------------------------------------------------
/doc-pictures/Spacers.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TalmudLab/daf-renderer/b2818adfd245e4a4857ad941d1d76e6e2eb42c62/doc-pictures/Spacers.PNG
--------------------------------------------------------------------------------
/doc-pictures/Stairs Case.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TalmudLab/daf-renderer/b2818adfd245e4a4857ad941d1d76e6e2eb42c62/doc-pictures/Stairs Case.PNG
--------------------------------------------------------------------------------
/doc-pictures/Three Cases.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TalmudLab/daf-renderer/b2818adfd245e4a4857ad941d1d76e6e2eb42c62/doc-pictures/Three Cases.PNG
--------------------------------------------------------------------------------
/examples/edgeCase.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Daf Renderer Example 2
7 |
8 |
9 |
10 |
11 |
12 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/examples/example2.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Daf Renderer Example 2
7 |
8 |
9 |
10 |
11 |
12 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/examples/fonts/Mekorot-Rashi.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TalmudLab/daf-renderer/b2818adfd245e4a4857ad941d1d76e6e2eb42c62/examples/fonts/Mekorot-Rashi.ttf
--------------------------------------------------------------------------------
/examples/fonts/Mekorot-Vilna-Bold-Italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TalmudLab/daf-renderer/b2818adfd245e4a4857ad941d1d76e6e2eb42c62/examples/fonts/Mekorot-Vilna-Bold-Italic.ttf
--------------------------------------------------------------------------------
/examples/fonts/Mekorot-Vilna-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TalmudLab/daf-renderer/b2818adfd245e4a4857ad941d1d76e6e2eb42c62/examples/fonts/Mekorot-Vilna-Bold.ttf
--------------------------------------------------------------------------------
/examples/fonts/Mekorot-Vilna-Italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TalmudLab/daf-renderer/b2818adfd245e4a4857ad941d1d76e6e2eb42c62/examples/fonts/Mekorot-Vilna-Italic.ttf
--------------------------------------------------------------------------------
/examples/fonts/Mekorot-Vilna.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TalmudLab/daf-renderer/b2818adfd245e4a4857ad941d1d76e6e2eb42c62/examples/fonts/Mekorot-Vilna.ttf
--------------------------------------------------------------------------------
/examples/main.js:
--------------------------------------------------------------------------------
1 | /*-------------------------------------------------------*/
2 | // THIS FILE IS NOT MEANT TO BE INCLUDED IN THE FINAL PACKAGE
3 | // IT IS A REMEMNANT OF EARLIER DEVELOPMENT FOR CALLING THE SEFARIA API
4 | // DO NOT PAY IT ANY ATTENTION
5 | // EVENTUALLY WE WILL TURN THIS INTO A CLEAR EXAMPLE OF HOW TO CALL THE SEFARIA API
6 | /*-------------------------------------------------------*/
7 |
8 |
9 | const texts = { main: [], rashi: [], tosafot: [], sefariaTranslations: []};
10 | const spans = { main: [], rashi: [], tosafot: []};
11 | const sentenceSpans = { main: [] }
12 | const charIndexToSpan = { main: [], rashi: [], tosafot: []} // set when getSpanByCharIndex is first called
13 | const spanPrefixes = { main: 'word-main', rashi: 'word-rashi', tosafot: 'word-tosafot', sentence: 'sentence-main' };
14 |
15 | let tractate = 'Brachot';
16 | let daf = 42; //Set What daf you want
17 | let amud = 'b'; // Set what amud you want, a or bd
18 | let hebrewRef = '';
19 | let next = []; //tractate, daf, amud; e.g., ['Eruvin', '13', a']
20 | let prev = []; //tractate, daf, amud
21 |
22 |
23 | window.addEventListener("load", () => {
24 | getTexts(tractate, daf, amud);
25 | })
26 | function getTexts(tractate, daf, amud) {
27 | //For now this is how we are accessing Texts
28 | commentary = checkRashbam(tractate, daf, amud);
29 |
30 | const uri1 = 'https://www.sefaria.org/api/texts/' + tractate + '.' + daf + amud + '?' + 'vhe=Wikisource_Talmud_Bavli',
31 | uri2 = 'https://www.sefaria.org/api/texts/' + commentary +'_on_' + tractate + '.' + daf + amud + '.1-100' + '?',
32 | uri3 = 'https://www.sefaria.org/api/texts/Tosafot_on_' + tractate + '.' + daf + amud + '.1-100' + '?';
33 |
34 | const renderer = dafRenderer("#daf", { padding: { vertical: "10px"}});
35 |
36 | Promise.all([jsonp(uri1), jsonp(uri2), jsonp(uri3)]).then(values => {
37 |
38 | const main = values[0];
39 | const rashi = values[1];
40 | const tosafot = values[2];
41 |
42 | //Set next and prev for the arrow buttons.
43 | //Sefaria does give you next and prev... sometimes. So we're not relying on that.
44 | // const tractateDetails = tractates[tractate];
45 | // if (daf + amud == tractateDetails.lastPage) {
46 | // next = [tractateDetails.nextTractate, 2, a]
47 | // }
48 | // else {
49 | // next = (amud == 'a') ? [tractate, daf, 'b'] : [tractate, daf + 1, 'a'];
50 | // }
51 | // if (daf + amud == '2a') {
52 | // const lastPage = tractates[tractateDetails.prevTractate].lastPage;
53 | // const lastPageAmud = lastPage.slice(-1);
54 | // const lastPageDaf = lastPage.slice(0, -1)
55 | // prev = [tractateDetails.prevTractate, lastPageDaf, lastPageAmud];
56 | // }
57 | // else {
58 | // prev = (amud == 'a') ? [tractate, daf - 1, 'b'] : [tractate, daf, 'a'];
59 | // }
60 | // next = main.next.split(/ (?=\d)|(?=[ab])/g);
61 | // prev = main.prev.split(/ (?=\d)|(?=[ab])/g);
62 |
63 | heRef = main.heRef;
64 |
65 | heRefArray = main.heRef.replace('״', '').split(' ');
66 | if (heRefArray.length == 4) {
67 | heRefArray[0] += ' ' + heRefArray[1];
68 | heRefArray.splice(1,1);
69 | }
70 |
71 | // setTitle(heRefArray[0], tractate, heRefArray[1], daf, heRefArray[2] == 'א');
72 |
73 |
74 | texts.rashi = reformatRashi(rashi.he);
75 | texts.tosafot = reformatTosafot(tosafot.he);
76 | texts.main = reformatMain(main.he)
77 | texts.sefariaTranslations = main.text;
78 |
79 | sentenceSpans.main = texts.main.map(
80 | (sentence, index) => { return { sentence: sentence, id: `${spanPrefixes.sentence}-${index}`}})
81 |
82 | //This code works, and I'm sure it could use some refactoring
83 | //(It's the word-span-wrapping function, but now it has to not step on the toes of the bolding functionality)
84 | const processText = (text, prefix, indexStart = 0) => text
85 | .split(/(\<[^<>]+\>[^<>]+<[^<>]+>)/g) //Break out all HTML elements
86 | .join('***') //Join using so we can do another split
87 | .split(/ (?![^<]*>|<)/g) //Break out all spaces that are not preceeding tags or within tag declarations
88 | .join('***') //Join so we're back to one string, with everything separated by commas
89 | .split('***') //Split into a final, one-dimensional array of words
90 | .filter(Boolean) //Get rid of empty elements
91 | .map(n => n.includes('img') ? wrapImage(n) : n) //Wrap image elements with hover buttons that show and hide them
92 | .map( (word, index) => { return {word: word, id: `${prefix}-${index + indexStart}`} });
93 |
94 | let nthVerse = 0;
95 | const cutVerses = string => {
96 | const verse = /\(([^\(\)]*,[^\(\)]*)\)/g;
97 | return string.replace(verse, (match, verseRef) => verseLinkSpan(verseRef, ++nthVerse));
98 | }
99 |
100 | const wordToHTML = spanObj => wordSpan(spanObj.id, spanObj.word);
101 | const sentenceToHTML = spanObj => sentenceSpan(spanObj.id, spanObj.sentence);
102 |
103 | sentenceSpans.main = sentenceSpans.main.map( (sentenceObj) => {
104 | const wordSpanObj =
105 | processText(
106 | cutVerses(sentenceObj.sentence),
107 | spanPrefixes.main,
108 | spans.main.length
109 | )
110 | .map(wordSpanObj => Object.assign(wordSpanObj, {sentence: sentenceObj.id}));
111 | spans.main = spans.main.concat(wordSpanObj); //OY A SIDE EFFECT (refactor? though this is more efficient than looping twice)
112 | return {
113 | id: sentenceObj.id,
114 | sentence: wordSpanObj.map(wordToHTML).join(' ')
115 | }
116 | })
117 |
118 | spans.rashi = processText(texts.rashi, spanPrefixes.rashi);
119 | spans.tosafot = processText(texts.tosafot, spanPrefixes.tosafot);
120 |
121 | const mainHTML = sentenceSpans.main.map(sentenceToHTML).join(' ');
122 | const rashiHTML = spans.rashi.map(wordToHTML).join(' ')
123 | const tosafotHTML = spans.tosafot.map(wordToHTML).join(' ')
124 |
125 | console.log("Main:", mainHTML)
126 | console.log("Rashi:", rashiHTML)
127 | console.log("Tosafot:", tosafotHTML)
128 | console.log("Amud:", amud)
129 |
130 |
131 | // document.getElementById('maintxtcntr').innerHTML = "" + mainHTML + "";
132 | // document.getElementById('innertxtcntr').innerHTML = "" + rashiHTML + "";
133 | // document.getElementById('outertxtcntr').innerHTML = "" + tosafotHTML + "";;
134 |
135 | setTimeout(function(){
136 | renderer.render(mainHTML, rashiHTML, tosafotHTML, amud);
137 | // document.querySelector('.daf-container').style.opacity = '1';
138 | // changePadding();
139 |
140 | // Should probably be its own function, but need to do it after all of this and couldnt figure out how
141 | // spaceLastLine('maintxtcontent');
142 | }, 10)
143 |
144 | })
145 |
146 | }
147 |
148 |
149 | //TITLE CONTROLS
150 | // document.querySelector('.daf-button-prev').addEventListener('click', prevPage);
151 | // document.querySelector('.daf-button-next').addEventListener('click', nextPage);
152 | //
153 | // document.querySelector('.daf-title-tractate-dropdown').addEventListener('change', event => {
154 | // tractate = event.target.value;
155 | // getTexts(tractate, daf, amud);
156 | // });
157 | //
158 | // document.querySelector('.daf-page-dropdown').addEventListener('change', event => {
159 | // amud = event.target.value.slice(-1);
160 | // daf = event.target.value.slice(0, -1);
161 | // getTexts(tractate, daf, amud);
162 | // });
163 | //
164 | // function nextPage() {
165 | // getTexts(...next);
166 | // }
167 | //
168 | // function prevPage() {
169 | // getTexts(...prev);
170 | // }
171 | //
172 | // function setTitle(mesekhet, tractate, daf, page, amudAlef = true) {
173 | // document.querySelector('.daf-title-tractate-hebrew').innerText = mesekhet;
174 | // document.querySelector('.daf-title-page').innerText = (amudAlef ? '.' : ':') + daf ;
175 | // document.querySelector('.daf-title-tractate-dropdown').value = tractate;
176 | //
177 | // const dafSelection = document.querySelector('.daf-page-dropdown');
178 | // const currTractateLength = tractates[tractate].length;
179 | //
180 | // if (dafSelection.options.length < currTractateLength) {
181 | // for (let i = dafSelection.options.length; i < currTractateLength; i++){
182 | // const amudA = i % 2 == 0;
183 | // const daf = 2 + (i - (i % 2)) / 2;
184 | // const dafString = daf + (amudA ? 'a' : 'b');
185 | // const opt = document.createElement('option');
186 | // opt.value = dafString;
187 | // opt.innerHTML = dafString;
188 | // dafSelection.appendChild(opt);
189 | // }
190 | // } else {
191 | // //dafSelection.options and its indexes will change as we delete items
192 | // const difference = dafSelection.options.length - currTractateLength;
193 | // for (let i = 0; i < difference; i++) {
194 | // dafSelection.options.remove(currTractateLength);
195 | // }
196 | // }
197 | //
198 | // document.querySelector('.daf-page-dropdown').value = page + (amudAlef ? 'a' : 'b');
199 | // }
200 |
201 | // function getHorizontalPos(el) {
202 | // const wdth = document.getElementsByClassName('maintxtcontent')[0].offsetWidth;
203 | // for (var lx_left=0, lx_right=0;
204 | // el != null;
205 | // lx_left += el.offsetLeft, lx_right = wdth - lx_left, el = el.offsetParent);
206 | // return {left: lx_left, right: lx_right};
207 | // }
208 | //TEXT PROCESSING AND HTML GENERATION
209 | const wordSpan = (id, word) => `${word}`
210 |
211 | const sentenceSpan = (id, sentence) => `${sentence}`
212 |
213 | const verseLinkSpan = (verse, number) =>
214 | "${number}`
217 |
218 | let nthImage = 0;
219 | const wrapImage = imageEl =>
220 | `
226 |
227 | ${imageEl}
228 | `
229 | const wrapSentences = sentenceArray => sentenceArray.map(
230 | (sentence, index) => sentenceSpan(sentence, `${spanPrefixes.sentence}-${index}`))
231 |
232 | const reformatMain = text =>
233 | text.map(string => string
234 | .replace(/:,/g, ": ")
235 | .replace(//g, "")
236 | .replace(/<\/strong>/g, ""))
237 |
238 | const reformatRashi = text =>
239 | text.filter(arr => arr.length > 0)
240 | .map(subarr => subarr.map(str =>
241 | str.replace(/([^-:]*) - ([^-:]+:)/, ("$2 "))
242 | ).join(''))
243 | .join('')
244 | .replace(/,,/g, "")
245 | .replace(/,:/g, ": ")
246 | .replace(/:,/g, ": ")
247 |
248 | const reformatTosafot = text =>
249 | text.filter(arr => arr.length > 0)
250 | .map(subarr => subarr.map(str =>
251 | str.replace(/([^-:]*) - ([^-:]+:)/, ("$2 "))
252 | ).join(''))
253 | .join('')
254 | .replace(/,,/g, "")
255 | .replace(/,:/g, ": ")
256 | .replace(/:,/g, ": ")
257 |
258 | //SPAN ACCESS
259 | // function getSpanByWordIndex(text, index) {
260 | // return spans[text][index];
261 | // }
262 | //
263 | // function getSpanByWordId(text, id) {
264 | // const index = id.match(/\d+/)[0];
265 | // return spans[text][index];
266 | // }
267 | //
268 | // function getSpanByCharIndex(text, index) {
269 | // if (charIndexToSpan[text].length) {
270 | // return charIndexToSpan[text][index];
271 | // }
272 | // else {
273 | // let spaceCount = 0;
274 | // let returnVal;
275 | // for (let i = 0; i < texts[text].length; i++) {
276 | // if (texts[text][i] === ' ') {
277 | // spaceCount++;
278 | // }
279 | // const span = spans[text][spaceCount];
280 | // charIndexToSpan[text][i] = span;
281 | // if (i === index) {
282 | // returnVal = span;
283 | // }
284 | // }
285 | // return returnVal;
286 | // }
287 | //
288 | // }
289 | //
290 | // function getSpansBySearch(text, query) {
291 | // const regex = new RegExp(query, 'gi');
292 | // let spans = [], result;
293 | // while ( (result = regex.exec(texts[text])) ) {
294 | // spans.push(getSpanByCharIndex(text, result.index));
295 | // }
296 | // return spans;
297 | // }
298 | //
299 | // function getSefariaTranslation(sentenceIndex) {
300 | // return texts.sefariaTranslations[sentenceIndex];
301 | // }
302 | //
303 | // //Verse number links
304 | // function verseLinkHover(verse) {
305 | // //alert(verse);
306 | // }
307 | //
308 | // function verseLinkClick(verse) {
309 | // ajaxGet("https://sefaria.org/api/name/" + verse).then((data) =>
310 | // window.open("https://sefaria.org/" + data.url))
311 | // }
312 | //
313 | // function imageButtonEnter(imageNumber) {
314 | // document.querySelector('.image-' + imageNumber).style.display = 'block';
315 | // }
316 | // function imageButtonLeave(imageNumber) {
317 | // document.querySelector('.image-' + imageNumber).style.display = 'none';
318 | // }
319 |
320 |
321 | function checkRashbam (tractate, daf, amud) {
322 | if(tractate === "Bava Batra" && daf >= 30){
323 | return ("Rashbam");
324 | }
325 | else{
326 | return("Rashi");
327 | }
328 | };
329 |
330 |
331 | //This is our function to fetch data with Sefaria's API
332 | function jsonp(uri) {
333 | return new Promise(function (resolve, reject) {
334 | var id = '_' + Math.round(10000 * Math.random());
335 | var callbackName = 'jsonp_callback_' + id;
336 | window[callbackName] = function (data) {
337 | delete window[callbackName];
338 | var ele = document.getElementById(id);
339 | ele.parentNode.removeChild(ele);
340 | resolve(data);
341 | }
342 |
343 | var src = uri + '&callback=' + callbackName;
344 | var script = document.createElement('script');
345 | script.src = src;
346 | script.id = id;
347 | script.addEventListener('error', reject);
348 | (document.getElementsByTagName('head')[0] || document.body || document.documentElement).appendChild(script)
349 | });
350 | }
351 |
352 | function ajaxGet(uri, log) {
353 | return new Promise(function(resolve, reject) {
354 | const request = new XMLHttpRequest();
355 | request.open("GET", uri, true);
356 | request.send();
357 | request.onreadystatechange = (e) => {
358 | if (request.readyState === XMLHttpRequest.DONE) {
359 | if (request.status === 200) {
360 | try {
361 | resolve(JSON.parse(request.response));
362 | } catch (e) {
363 | resolve(request.response);
364 | }
365 | } else {
366 | reject(request.status);
367 | }
368 | } else {
369 | if (log) {
370 | console.log("Request readyState: " + request.readyState)
371 | }
372 | }
373 | }
374 | });
375 | }
376 |
377 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "daf-renderer",
3 | "version": "1.1.4",
4 | "description": "A DOM render library for creating Talmud pages on the web.",
5 | "main": "dist/daf-renderer.umd.js",
6 | "module": "dist/daf-renderer.esm.js",
7 | "browser": "dist/daf-renderer.umd.js",
8 | "scripts": {
9 | "build": "rollup -c -w"
10 | },
11 | "contributors": [
12 | {
13 | "name": "Dan Jutan",
14 | "email": "danjutan@gmail.com"
15 | },
16 | {
17 | "name": "Shaun Regenbaum",
18 | "email": "shaunregenbaum@gmail.com"
19 | }
20 | ],
21 | "license": "MIT",
22 | "devDependencies": {
23 | "postcss": "^8.2.1",
24 | "rollup": "^2.36.1",
25 | "rollup-plugin-postcss": "^4.0.0",
26 | "rollup-plugin-terser": "^7.0.2"
27 | },
28 | "repository": {
29 | "type": "git",
30 | "url": "https://github.com/GT-Jewish-DH/daf-renderer.git"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import pkg from './package.json';
2 | import postcss from 'rollup-plugin-postcss';
3 | import {terser} from "rollup-plugin-terser";
4 | export default [
5 | // browser-friendly UMD build
6 | {
7 | input: 'src/renderer.js',
8 | output: [
9 | {
10 | name: 'dafRenderer',
11 | file: pkg.browser,
12 | format: 'umd'
13 | },
14 | {
15 | file: pkg.module,
16 | format: 'es'
17 | },
18 | {
19 | name: "dafRenderer",
20 | file: "dist/daf-renderer.min.js",
21 | format: "umd",
22 | plugins: [terser()]
23 | }
24 | ],
25 | plugins: [
26 | postcss({
27 | modules: true,
28 | plugins: []
29 | })
30 | ]
31 | },
32 | ];
33 |
--------------------------------------------------------------------------------
/src/calculate-spacers-breaks.js:
--------------------------------------------------------------------------------
1 | function getLineInfo(text, font, fontSize, lineHeight, dummy) {
2 | dummy.innerHTML = "";
3 | let testDiv = document.createElement("span");
4 | testDiv.style.font = fontSize + " " + String(font);
5 | testDiv.style.lineHeight = String(lineHeight) + "px";
6 | testDiv.innerHTML = text;
7 | testDiv.style.position = "absolute";
8 | dummy.append(testDiv);
9 | const rect = testDiv.getBoundingClientRect();
10 | const height = rect.height;
11 | const width = rect.width;
12 | const widthProportional = width / dummy.getBoundingClientRect().width;
13 | testDiv.remove();
14 | return {height, width, widthProportional};
15 | }
16 |
17 | function heightAccumulator(font, fontSize, lineHeight, dummy) {
18 | return (lines) => {
19 | return getLineInfo(lines.join("
"), font, fontSize, lineHeight, dummy).height;
20 | }
21 | }
22 |
23 | function getBreaks(sizeArray) {
24 | const widths = sizeArray.map(size => size.widthProportional);
25 | const diffs = widths.map((width, index, widths) => index == 0 ? 0 : Math.abs(width - widths[index - 1]));
26 | const threshold = 0.12;
27 | let criticalPoints = diffs.reduce((indices, curr, currIndex) => {
28 | //Breaks before line 4 are flukes
29 | if (currIndex < 4) return indices;
30 | if (curr > threshold) {
31 | //There should never be two breakpoints in a row
32 | const prevIndex = indices[indices.length - 1];
33 | if (prevIndex && (currIndex - prevIndex) == 1) {
34 | return indices;
35 | }
36 | indices.push(currIndex);
37 | }
38 | return indices;
39 | }, []);
40 | const averageAround = points => points.map((point, i) => {
41 | let nextPoint;
42 | if (!nextPoint) {
43 | nextPoint = Math.min(point + 3, widths.length - 1);
44 | }
45 | let prevPoint;
46 | if (!prevPoint) {
47 | prevPoint = Math.max(point - 3, 0);
48 | }
49 | /*
50 | Note that these are divided by the width of the critical point line such that
51 | we get the average width of the preceeding and proceeding chunks *relative*
52 | to the critical line.
53 | */
54 | const before = (widths.slice(prevPoint, point).reduce((acc, curr) => acc + curr) /
55 | (point - prevPoint)) / widths[point];
56 | let after;
57 | if ( point + 1 >= nextPoint) {
58 | after = widths[nextPoint] / widths[point];
59 | } else {
60 | after =(widths.slice(point + 1, nextPoint).reduce((acc, curr) => acc + curr) /
61 | (nextPoint - point - 1)) / widths[point];
62 | }
63 | return {
64 | point,
65 | before,
66 | after,
67 | diff: Math.abs(after - before)
68 | }
69 | })
70 | const aroundDiffs = averageAround(criticalPoints)
71 | .sort( (a,b) => b.diff - a.diff);
72 | criticalPoints = aroundDiffs
73 | .filter( ({diff}) => diff > 0.22)
74 | .map( ({point}) => point)
75 | return criticalPoints.sort( (a, b) => a - b);
76 | }
77 |
78 | export function onlyOneCommentary(lines, options, dummy) {
79 | const fontFamily = options.fontFamily.inner;
80 | const fontSize = options.fontSize.side;
81 | const lineHeight = parseFloat(options.lineHeight.side);
82 | const sizes = lines.map(text => getLineInfo(text, fontFamily, fontSize, lineHeight, dummy));
83 | const breaks = getBreaks(sizes);
84 | if (breaks.length == 3) {
85 | const first = lines.slice(0, breaks[1]);
86 | const second = lines.slice(breaks[1]);
87 | return [first, second];
88 | }
89 | }
90 |
91 | export function calculateSpacersBreaks(mainArray, rashiArray, tosafotArray, options, dummy) {
92 | const lines = {
93 | main: mainArray,
94 | rashi: rashiArray,
95 | tosafot: tosafotArray
96 | }
97 |
98 | const parsedOptions = {
99 | padding: {
100 | vertical: parseFloat(options.padding.vertical),
101 | horizontal: parseFloat(options.padding.horizontal)
102 | },
103 | halfway: 0.01 * parseFloat(options.halfway),
104 | fontFamily: options.fontFamily, // Object of strings
105 | fontSize: {
106 | main: options.fontSize.main,
107 | side: options.fontSize.side,
108 | },
109 | lineHeight: {
110 | main: parseFloat(options.lineHeight.main),
111 | side: parseFloat(options.lineHeight.side),
112 | },
113 | }
114 |
115 |
116 | const mainOptions = [parsedOptions.fontFamily.main, parsedOptions.fontSize.main, parsedOptions.lineHeight.main];
117 | const commentaryOptions = [parsedOptions.fontFamily.inner, parsedOptions.fontSize.side, parsedOptions.lineHeight.side];
118 |
119 | const sizes = {};
120 | sizes.main = lines.main.map(text => getLineInfo(text, ...mainOptions, dummy));
121 | ["rashi", "tosafot"].forEach(text => {
122 | sizes[text] = lines[text].map(line => getLineInfo(line, ...commentaryOptions, dummy))
123 | })
124 |
125 | const accumulateMain = heightAccumulator(...mainOptions, dummy);
126 | const accumulateCommentary = heightAccumulator(...commentaryOptions, dummy);
127 |
128 | const breaks = {};
129 |
130 | ["rashi", "tosafot", "main"].forEach(text => {
131 | breaks[text] = getBreaks(sizes[text])
132 | /*
133 | Hadran lines aren't real candidates for line breaks.
134 | TODO: Extract this behavior , give it an option/parameter
135 | */
136 | .filter(lineNum => !(lines[text][lineNum].includes("hadran"))
137 | )
138 | })
139 |
140 |
141 | const spacerHeights = {
142 | start: 4.4 * parsedOptions.lineHeight.side,
143 | inner: null,
144 | outer: null,
145 | end: 0,
146 | exception: 0
147 | };
148 |
149 | const mainHeight = accumulateMain(lines.main);
150 | const mainHeightOld = (sizes.main.length) * parsedOptions.lineHeight.main;
151 | let afterBreak = {
152 | inner: accumulateCommentary(lines.rashi.slice(4)),
153 | outer: accumulateCommentary(lines.tosafot.slice(4))
154 | }
155 |
156 | let afterBreakOld = {
157 | inner: parsedOptions.lineHeight.side * (sizes.rashi.length - 4),
158 | outer: parsedOptions.lineHeight.side * (sizes.tosafot.length - 4)
159 | }
160 |
161 | if (breaks.rashi.length < 1 || breaks.tosafot.length < 1) {
162 | console.log("Dealing with Exceptions")
163 | if (breaks.rashi.length < 1) {
164 | afterBreak.inner = parsedOptions.lineHeight.side * (sizes.rashi.length + 1)
165 | spacerHeights.exception = 2
166 | }
167 | if (breaks.tosafot.length < 1) {
168 | afterBreak.outer = parsedOptions.lineHeight.side * (sizes.tosafot.length + 1)
169 | spacerHeights.exception = 2
170 | }
171 | }
172 | switch (breaks.main.length) {
173 | case 0:
174 | spacerHeights.inner = mainHeight;
175 | spacerHeights.outer = mainHeight;
176 | if (breaks.rashi.length == 2) {
177 | spacerHeights.end = accumulateCommentary(lines.rashi.slice(breaks.rashi[1]))
178 | } else {
179 | spacerHeights.end = accumulateCommentary(lines.tosafot.slice(breaks.tosafot[1]))
180 | }
181 | console.log("Double wrap")
182 | break;
183 | case 1:
184 | if (breaks.rashi.length != breaks.tosafot.length) {
185 | if (breaks.tosafot.length == 0) {
186 | spacerHeights.outer = 0;
187 | spacerHeights.inner = afterBreak.inner;
188 | break;
189 | }
190 | if (breaks.rashi.length == 0) {
191 | spacerHeights.inner = 0;
192 | spacerHeights.outer = afterBreak.outer;
193 | break;
194 | }
195 | let stair;
196 | let nonstair;
197 | if (breaks.rashi.length == 1) {
198 | stair = "outer";
199 | nonstair = "inner";
200 | } else {
201 | stair = "inner";
202 | nonstair = "outer";
203 | }
204 | spacerHeights[nonstair] = afterBreak[nonstair];
205 | spacerHeights[stair] = mainHeight;
206 | console.log("Stairs")
207 | break;
208 | }
209 | case 2:
210 | spacerHeights.inner = afterBreak.inner;
211 | spacerHeights.outer = afterBreak.outer;
212 | console.log("Double Extend")
213 | break;
214 | default:
215 | spacerHeights.inner = afterBreak.inner;
216 | spacerHeights.outer = afterBreak.outer;
217 | console.log("No Case Exception")
218 | break;
219 | }
220 | return spacerHeights;
221 | }
222 |
--------------------------------------------------------------------------------
/src/calculate-spacers.js:
--------------------------------------------------------------------------------
1 | function getAreaOfText(text, font, fs, width, lh, dummy) {
2 | let testDiv = document.createElement("div");
3 | testDiv.style.font = String(fs) + "px " + String(font);
4 | testDiv.style.width = String(width) + "px"; //You can remove this, but it may introduce unforseen problems
5 | testDiv.style.lineHeight = String(lh) + "px";
6 | testDiv.innerHTML = text;
7 | dummy.append(testDiv);
8 | let test_area = Number(testDiv.clientHeight * testDiv.clientWidth);
9 | testDiv.remove();
10 | return test_area;
11 | }
12 |
13 | function calculateSpacers(mainText, innerText, outerText, options, dummy) {
14 |
15 | const parsedOptions = {
16 | width: parseFloat(options.contentWidth),
17 | padding: {
18 | vertical: parseFloat(options.padding.vertical),
19 | horizontal: parseFloat(options.padding.horizontal)
20 | },
21 | halfway: 0.01 * parseFloat(options.halfway),
22 | fontFamily: options.fontFamily,
23 | fontSize: {
24 | main: parseFloat(options.fontSize.main),
25 | side: parseFloat(options.fontSize.side),
26 | },
27 | lineHeight: {
28 | main: parseFloat(options.lineHeight.main),
29 | side: parseFloat(options.lineHeight.side),
30 | },
31 | mainWidth: 0.01 * parseFloat(options.mainWidth)
32 | }
33 |
34 | const midWidth = Number(parsedOptions.width * parsedOptions.mainWidth) - 2*parsedOptions.padding.horizontal; //main middle strip
35 | const topWidth = Number(parsedOptions.width * parsedOptions.halfway) - parsedOptions.padding.horizontal; //each commentary top
36 | const sideWidth = Number(parsedOptions.width * (1 - parsedOptions.mainWidth)/2) //each commentary widths, dont include padding, sokeep it constant
37 |
38 | const spacerHeights = {
39 | start: 4.3 * parsedOptions.lineHeight.side,
40 | inner: null,
41 | outer: null,
42 | end: 0,
43 | exception: 0
44 | };
45 |
46 | // We are accounting for the special case, where you have line breaks:
47 | // if (options.lineBreaks) {
48 | // console.log("Special Case for Line Breaks")
49 | // const main = {
50 | // name: "main",
51 | // text: mainText,
52 | // lineHeight: parsedOptions.lineHeight.main,
53 | // top: 0,
54 | // }
55 | // const outer = {
56 | // name: "outer",
57 | // text: outerText,
58 | // lineHeight: parsedOptions.lineHeight.side,
59 | // top: 4,
60 | // }
61 | // const inner = {
62 | // name: "inner",
63 | // text: innerText,
64 | // lineHeight: parsedOptions.lineHeight.side,
65 | // top: 4,
66 | // }
67 | //
68 | // const texts = [main, outer, inner];
69 | // texts.forEach(body => body.brCount = (body.text.match(/
/g) || []).length - body.top);
70 | // texts.forEach(body => body.height = (body.brCount * body.lineHeight));
71 | // texts.forEach(body => body.unadjustedHeight = ((body.brCount + body.top + 1) * body.lineHeight));
72 | // texts.forEach(body => body.unadjustedHeightAlt = ((body.brCount + body.top) * body.lineHeight)*sideWidth/topWidth);
73 | // const perHeight = Array.from(texts).sort((a, b) => a.height - b.height);
74 | //
75 | // const exConst = 2.2
76 | //
77 | // //Checking Exceptions:
78 | // if (inner.unadjustedHeight <= 0 && outer.unadjustedHeight <= 0){
79 | // console.error("No Commentary");
80 | // return Error("No Commentary");
81 | // };
82 | // if (inner.unadjustedHeightAlt/exConst < spacerHeights.start || outer.unadjustedHeightAlt/exConst < spacerHeights.start) {
83 | // console.log("Exceptions")
84 | // if (inner.unadjustedHeightAlt/exConst <= spacerHeights.start) {
85 | // spacerHeights.inner = inner.unadjustedHeight;
86 | // spacerHeights.outer = outer.height
87 | // return spacerHeights;
88 | // }
89 | // if (outer.unadjustedHeightAlt/exConst <= spacerHeights.start) {
90 | // spacerHeights.outer = outer.unadjustedHeight;
91 | // spacerHeights.inner = inner.height;
92 | // return spacerHeights;
93 | // }
94 | // else {
95 | // return Error("Inner Spacer Error");
96 | // }
97 | // };
98 | // //If Double=Wrap
99 | // if (perHeight[0].name === "main"){
100 | // console.log("Double-Wrap");
101 | // spacerHeights.inner = main.height;
102 | // spacerHeights.outer = main.height;
103 | //
104 | // const brDifference = perHeight[1].brCount - perHeight[0].brCount;
105 | // spacerHeights.end = brDifference*perHeight[1].lineHeight;
106 | // return spacerHeights;
107 | // }
108 | //
109 | // //If Stairs
110 | // if (perHeight[1].name === "main") {
111 | // console.log("Stairs");
112 | // spacerHeights[perHeight[0].name] = perHeight[0].height;
113 | // spacerHeights[perHeight[2].name] = main.height;
114 | // return spacerHeights;
115 | // }
116 | //
117 | // //If Double Extend
118 | // console.log("Double-Extend")
119 | // spacerHeights.inner = inner.height + (inner.height/inner.height**2)*inner.lineHeight;
120 | // spacerHeights.outer = outer.height + (inner.height/inner.height**2)*outer.lineHeight;
121 | // return spacerHeights
122 | // }
123 |
124 |
125 | // We could probably put this somewhere else, it was meant to be a place for all the padding corrections,
126 | // but there turned out to only be one
127 | const paddingAreas = {
128 | name: "paddingAreas",
129 | horizontalSide: sideWidth * parsedOptions.padding.vertical,
130 | }
131 |
132 |
133 | const topArea = (lineHeight) => ((4 * lineHeight * topWidth)); //remove area of the top 4 lines
134 |
135 |
136 | const main = {
137 | name: "main",
138 | width: midWidth,
139 | text: mainText,
140 | lineHeight: parsedOptions.lineHeight.main,
141 | area: getAreaOfText(mainText, parsedOptions.fontFamily.main, parsedOptions.fontSize.main, midWidth, parsedOptions.lineHeight.main, dummy),
142 | length: null,
143 | height: null,
144 | }
145 | const outer = {
146 | name: "outer",
147 | width: sideWidth,
148 | text: outerText,
149 | lineHeight: parsedOptions.lineHeight.side,
150 | area: getAreaOfText(outerText, parsedOptions.fontFamily.outer, parsedOptions.fontSize.side, sideWidth, parsedOptions.lineHeight.side, dummy)
151 | - topArea(parsedOptions.lineHeight.side),
152 | length: null,
153 | height: null,
154 | }
155 | const inner = {
156 | name: "inner",
157 | width: sideWidth,
158 | text: innerText,
159 | lineHeight: parsedOptions.lineHeight.side,
160 | area:
161 | getAreaOfText(innerText, parsedOptions.fontFamily.inner, parsedOptions.fontSize.side, sideWidth, parsedOptions.lineHeight.side, dummy)
162 | - topArea(parsedOptions.lineHeight.side),
163 | length: null,
164 | height: null,
165 | }
166 |
167 | const texts = [main, outer, inner];
168 | texts.forEach(text => text.height = text.area / text.width);
169 | texts.forEach(text => text.unadjustedArea = text.area + topArea(parsedOptions.lineHeight.side));
170 | texts.forEach(text => text.unadjustedHeight = text.unadjustedArea / text.width);
171 |
172 | const perHeight = Array.from(texts).sort((a, b) => a.height - b.height);
173 |
174 | //There are Three Main Types of Case:
175 | //Double-Wrap: The main text being the smallest and commentaries wrapping around it
176 | //Stairs: The main text wrapping around one, but the other wrapping around it
177 | //Double-Extend: The main text wrapping around both commentaries
178 |
179 | //Main Text is Smallest: Double-Wrap
180 | //Main Text being Middle: Stairs
181 | //Main Text Being Largest: Double-Extend
182 |
183 | //First we need to check we have enough commentary to fill the first four lines
184 | if (inner.height <= 0 && outer.height <= 0){
185 | console.error("No Commentary");
186 | return Error("No Commentary");
187 | };
188 |
189 |
190 | // This is a case that we have to decice what to do with, when there is not enough commentary on both sides to fill the lines.
191 | if (inner.height <= spacerHeights.start && outer.height <= spacerHeights.start) {
192 | console.error("Not Enough Commentary to Fill Four Lines");
193 | return Error("Not Enough Commentary");
194 | };
195 |
196 | // We are going to deal with our first edge case when there is either only one commentary
197 | // Or where there is enough of one commentary, but not four lines of the other.
198 | if (inner.unadjustedHeight <= spacerHeights.start || outer.unadjustedHeight <= spacerHeights.start) {
199 | if (inner.unadjustedHeight <= spacerHeights.start) {
200 | spacerHeights.inner = inner.unadjustedHeight;
201 | spacerHeights.outer = (outer.unadjustedArea - parsedOptions.width * 4 * parsedOptions.lineHeight.side) / sideWidth;
202 | spacerHeights.exception = 1;
203 | return spacerHeights;
204 | }
205 | if (outer.unadjustedHeight <= spacerHeights.start) {
206 | spacerHeights.outer = outer.unadjustedHeight;
207 |
208 | spacerHeights.inner = (inner.unadjustedArea - parsedOptions.width * 4 * parsedOptions.lineHeight.side) / sideWidth;
209 | spacerHeights.exception = 2;
210 | return spacerHeights;
211 | }
212 | else {
213 | return Error("Inner Spacer Error");
214 | }
215 | };
216 |
217 | //If Double=Wrap
218 | if (perHeight[0].name === "main"){
219 | console.log("Double-Wrap");
220 | spacerHeights.inner = main.area/midWidth;
221 | spacerHeights.outer = spacerHeights.inner;
222 |
223 | const sideArea = spacerHeights.inner * sideWidth + paddingAreas.horizontalSide;
224 | const bottomChunk = perHeight[1].area - sideArea;
225 | const bottomHeight = bottomChunk / topWidth;
226 | spacerHeights.end = bottomHeight;
227 | return spacerHeights;
228 | }
229 | // If Stairs, there's one text at the bottom. We will call it THE stair.
230 | // The remaining two texts form a "block" that we must compare with that bottom text.
231 | const blockArea = (main.area + perHeight[0].area);
232 | const blockWidth = midWidth + sideWidth;
233 | const blockHeight = blockArea / blockWidth;
234 |
235 | const stair = (perHeight[1].name == "main") ? perHeight[2] : perHeight[1];
236 | const stairHeight = stair.area / stair.width;
237 |
238 | if (blockHeight < stairHeight) {
239 | console.log(`Stairs, ${stair.name} is the stair`);
240 | // This function accounts for extra space that is introduced by padding
241 | const lilArea = (height1, height2, horizPadding) => (horizPadding) * (height1 - height2);
242 | const smallest = perHeight[0];
243 | spacerHeights[smallest.name] = smallest.height;
244 | spacerHeights[stair.name] = (blockArea - lilArea(blockHeight, spacerHeights[smallest.name], parsedOptions.padding.horizontal)) / blockWidth;
245 | return spacerHeights
246 | }
247 | //If Double Extend
248 | console.log("Double-Extend")
249 | spacerHeights.inner = inner.height;
250 | spacerHeights.outer = outer.height;
251 |
252 | return spacerHeights
253 | }
254 |
255 |
256 | export default (calculateSpacers);
--------------------------------------------------------------------------------
/src/options.js:
--------------------------------------------------------------------------------
1 | const defaultOptions = {
2 | contentWidth: "600px",
3 | mainWidth: "50%",
4 | padding: {
5 | vertical: "10px",
6 | horizontal: "16px",
7 | },
8 | innerPadding: "4px",
9 | outerPadding: "4px",
10 | halfway: "50%",
11 | fontFamily: {
12 | inner: "Rashi",
13 | outer: "Rashi",
14 | main: "Vilna"
15 | },
16 | direction: "rtl",
17 | fontSize: {
18 | main: "15px",
19 | side: "10.5px"
20 | },
21 | lineHeight: {
22 | main: "17px",
23 | side: "14px",
24 | }
25 | }
26 |
27 | function mergeAndClone (modified, definitional = defaultOptions) {
28 | const newOptions = {};
29 | for (const key in definitional) {
30 | if (key in modified) {
31 | const defType = typeof definitional[key];
32 | if (typeof modified[key] !== defType) {
33 | console.error(`Option ${key} must be of type ${defType}; ${typeof modified[key]} was passed.`);
34 | }
35 | if (defType == "object") {
36 | newOptions[key] = mergeAndClone(modified[key], definitional[key])
37 | } else {
38 | newOptions[key] = modified[key];
39 | }
40 | } else {
41 | newOptions[key] = definitional[key];
42 | }
43 | }
44 | return newOptions;
45 | }
46 |
47 | export {defaultOptions, mergeAndClone}
48 |
--------------------------------------------------------------------------------
/src/renderer.js:
--------------------------------------------------------------------------------
1 | import {defaultOptions, mergeAndClone} from "./options";
2 | import calculateSpacers from "./calculate-spacers";
3 | import styleManager from "./style-manager";
4 | import {
5 | calculateSpacersBreaks,
6 | onlyOneCommentary
7 | } from "./calculate-spacers-breaks";
8 | import classes from './styles.css';
9 |
10 | function el(tag, parent) {
11 | const newEl = document.createElement(tag);
12 | if (parent) parent.append(newEl);
13 | return newEl;
14 | }
15 |
16 | function div(parent) {
17 | return el("div", parent);
18 | }
19 |
20 | function span(parent) {
21 | return el("span", parent);
22 | }
23 |
24 |
25 | export default function (el, options = defaultOptions) {
26 | const root = (typeof el === "string") ? document.querySelector(el) : el;
27 | if (!(root && root instanceof Element && root.tagName.toUpperCase() === "DIV")) {
28 | throw "Argument must be a div element or its selector"
29 | }
30 | const outerContainer = div(root);
31 | const innerContainer = div(root);
32 | const mainContainer = div(root);
33 | const dummy = div(root);
34 | dummy.id = "dummy";
35 | const containers = {
36 | el: root,
37 | dummy: dummy,
38 | outer: {
39 | el: outerContainer,
40 | spacers: {
41 | start: div(outerContainer),
42 | mid: div(outerContainer),
43 | end: div(outerContainer)
44 | },
45 | text: div(outerContainer)
46 | },
47 | inner: {
48 | el: innerContainer,
49 | spacers: {
50 | start: div(innerContainer),
51 | mid: div(innerContainer),
52 | end: div(innerContainer)
53 | },
54 | text: div(innerContainer)
55 | },
56 | main: {
57 | el: mainContainer,
58 | spacers: {
59 | start: div(mainContainer),
60 | inner: div(mainContainer),
61 | outer: div(mainContainer),
62 | },
63 | text: div(mainContainer)
64 | }
65 | }
66 |
67 | const textSpans = {
68 | main: span(containers.main.text),
69 | inner: span(containers.inner.text),
70 | outer: span(containers.outer.text)
71 | }
72 |
73 | const clonedOptions = mergeAndClone(options, defaultOptions);
74 |
75 | styleManager.applyClasses(containers);
76 | styleManager.updateOptionsVars(clonedOptions);
77 |
78 | let resizeEvent;
79 | return {
80 | classes,
81 | containers,
82 | spacerHeights: {
83 | start: 0,
84 | inner: 0,
85 | outer: 0,
86 | end: 0
87 | },
88 | amud: "a",
89 | render(main, inner, outer, amud = "a", linebreak, renderCallback, resizeCallback) {
90 | if (resizeEvent) {
91 | window.removeEventListener("resize", resizeEvent);
92 | }
93 | if (this.amud != amud) {
94 | this.amud = amud;
95 | styleManager.updateIsAmudB(amud == "b");
96 | }
97 | if (!linebreak) {
98 | this.spacerHeights = calculateSpacers(main, inner, outer, clonedOptions, containers.dummy);
99 | resizeEvent = () => {
100 | this.spacerHeights = calculateSpacers(main, inner, outer, clonedOptions, containers.dummy);
101 | styleManager.updateSpacersVars(this.spacerHeights);
102 | console.log("resizing");
103 | if (resizeCallback)
104 | resizeCallback();
105 | }
106 | window.addEventListener("resize", resizeEvent);
107 | }
108 | else {
109 | let [mainSplit, innerSplit, outerSplit] = [main, inner, outer].map( text => {
110 | containers.dummy.innerHTML = text;
111 | const divRanges = Array.from(containers.dummy.querySelectorAll("div")).map(div => {
112 | const range = document.createRange();
113 | range.selectNode(div);
114 | return range;
115 | })
116 |
117 | const brs = containers.dummy.querySelectorAll(linebreak);
118 | const splitFragments = []
119 | brs.forEach((node, index) => {
120 | const range = document.createRange();
121 | range.setEndBefore(node);
122 | if (index == 0) {
123 | range.setStart(containers.dummy, 0);
124 | } else {
125 | const prev = brs[index - 1];
126 | range.setStartAfter(prev);
127 | }
128 | divRanges.forEach( (divRange, i) => {
129 | const inBetween = range.compareBoundaryPoints(Range.START_TO_START, divRange) < 0 && range.compareBoundaryPoints(Range.END_TO_END, divRange) > 0;
130 | if (inBetween) {
131 | splitFragments.push(divRange.extractContents());
132 | divRanges.splice(i, 1);
133 | }
134 | });
135 |
136 | splitFragments.push(range.extractContents());
137 | })
138 |
139 | return splitFragments.map(fragment => {
140 | const el = document.createElement("div");
141 | el.append(fragment);
142 | return el.innerHTML;
143 | })
144 | });
145 |
146 | containers.dummy.innerHTML = "";
147 |
148 | const hasInner = innerSplit.length != 0;
149 | const hasOuter = outerSplit.length != 0;
150 |
151 | if (hasInner != hasOuter) {
152 | const withText = hasInner ? innerSplit : outerSplit;
153 | const fixed = onlyOneCommentary(withText, clonedOptions, dummy);
154 | if (fixed) {
155 | if (amud == "a") {
156 | innerSplit = fixed[0];
157 | outerSplit = fixed[1];
158 | } else {
159 | innerSplit = fixed[1];
160 | outerSplit = fixed[0];
161 | }
162 | inner = innerSplit.join('
');
163 | outer = outerSplit.join('
');
164 | }
165 | }
166 |
167 | this.spacerHeights = calculateSpacersBreaks(mainSplit, innerSplit, outerSplit, clonedOptions, containers.dummy);
168 | resizeEvent = () => {
169 | this.spacerHeights = calculateSpacersBreaks(mainSplit, innerSplit, outerSplit, clonedOptions, containers.dummy);
170 | styleManager.updateSpacersVars(this.spacerHeights);
171 | if (resizeCallback)
172 | resizeCallback();
173 | console.log("resizing")
174 | }
175 | window.addEventListener('resize', resizeEvent)
176 | }
177 | styleManager.updateSpacersVars(this.spacerHeights);
178 | styleManager.manageExceptions(this.spacerHeights);
179 | textSpans.main.innerHTML = main;
180 | textSpans.inner.innerHTML = inner;
181 | textSpans.outer.innerHTML = outer;
182 |
183 | const containerHeight = Math.max(...["main", "inner", "outer"].map(t => containers[t].el.offsetHeight));
184 | containers.el.style.height = `${containerHeight}px`;
185 | if (renderCallback)
186 | renderCallback();
187 | },
188 | }
189 | }
190 |
191 |
--------------------------------------------------------------------------------
/src/style-manager.js:
--------------------------------------------------------------------------------
1 | import classes from './styles.css';
2 |
3 | const sideSpacersClasses = {
4 | start: [classes.spacer, classes.start],
5 | mid: [classes.spacer, classes.mid],
6 | end: [classes.spacer, classes.end]
7 | }
8 |
9 | const containerClasses = {
10 | el: classes.dafRoot,
11 | outer: {
12 | el: classes.outer,
13 | spacers: sideSpacersClasses,
14 | text: classes.text,
15 | },
16 | inner: {
17 | el: classes.inner,
18 | spacers: sideSpacersClasses,
19 | text: classes.text,
20 | },
21 | main: {
22 | el: classes.main,
23 | spacers: {
24 | start: sideSpacersClasses.start,
25 | inner: [classes.spacer, classes.innerMid],
26 | outer: [classes.spacer, classes.outerMid]
27 | },
28 | text: classes.text
29 | }
30 | }
31 |
32 | function addClasses(element, classNames) {
33 | if (Array.isArray(classNames))
34 | element.classList.add(...classNames)
35 | else
36 | element.classList.add(classNames)
37 | }
38 |
39 | function setVars(object, prefix = "") {
40 | const varsRule = Array.prototype.find.call
41 | (document.styleSheets, sheet => sheet.rules[0].selectorText == `.${classes.dafRoot}`)
42 | .rules[0];
43 |
44 | Object.entries(object).forEach(([key, value]) => {
45 | if (typeof value == "string") {
46 | varsRule.style.setProperty(`--${prefix}${key}`, value);
47 | } else if (typeof value == "object") {
48 | setVars(value, `${key}-`);
49 | }
50 | })
51 | }
52 |
53 |
54 | let appliedOptions;
55 | export default {
56 | applyClasses(containers, classesMap = containerClasses) {
57 | for (const key in containers) {
58 | if (key in classesMap) {
59 | const value = classesMap[key];
60 | if (typeof value === "object" && !Array.isArray(value)) {
61 | this.applyClasses(containers[key], value);
62 | } else {
63 | addClasses(containers[key], value);
64 | }
65 | }
66 | }
67 | },
68 | updateOptionsVars(options) {
69 | appliedOptions = options;
70 | setVars(options)
71 | },
72 | updateSpacersVars(spacerHeights) {
73 | setVars(
74 | Object.fromEntries(
75 | Object.entries(spacerHeights).map(
76 | ([key, value]) => ([key, String(value) + 'px']))
77 | ),
78 | "spacerHeights-"
79 | );
80 | },
81 | updateIsAmudB(amudB) {
82 | setVars({
83 | innerFloat: amudB ? "right" : "left",
84 | outerFloat: amudB ? "left" : "right"
85 | })
86 | },
87 | manageExceptions(spacerHeights) {
88 | if (!spacerHeights.exception) {
89 | setVars({
90 | hasOuterStartGap: "0",
91 | hasInnerStartGap: "0",
92 | outerStartWidth: "50%",
93 | innerStartWidth: "50%",
94 | innerPadding: appliedOptions.innerPadding,
95 | outerPadding: appliedOptions.outerPadding,
96 | });
97 | return;
98 | }
99 | if (spacerHeights.exception === 1) {
100 | console.log("In Style Exception, Case 1")
101 | setVars({
102 | hasInnerStartGap: "1",
103 | innerStartWidth: "100%",
104 | outerStartWidth: "0%",
105 | innerPadding: "0px",
106 | outerPadding: "0px",
107 | })
108 | } else if (spacerHeights.exception === 2) {
109 | console.log("In Style Exception, Case 2")
110 | setVars({
111 | hasOuterStartGap: "1",
112 | outerStartWidth: "100%",
113 | innerStartWidth: "0%",
114 | innerPadding: "0px",
115 | outerPadding: "0px"
116 | })
117 | }
118 | }
119 | }
--------------------------------------------------------------------------------
/src/styles.css:
--------------------------------------------------------------------------------
1 | /*Keep this as the first rule in the file*/
2 | .dafRoot {
3 | --contentWidth: 0px;
4 | --padding-horizontal: 0px;
5 | --padding-vertical: 0px;
6 | --halfway: 50%;
7 |
8 | --fontFamily-inner: "Rashi";
9 | --fontFamily-outer: "Tosafot";
10 | --fontFamily-main: "Vilna";
11 | --direction: "rtl";
12 |
13 | --fontSize-main: 0px;
14 | --fontSize-side: 0px;
15 |
16 | --lineHeight-main: 0px;
17 | --lineHeight-side: 0px;
18 |
19 | --mainWidth: 0%;
20 | --mainMargin-start: var(--mainWidth);
21 | --sidePercent: calc(calc(100% - var(--mainMargin-start)) / 2);
22 | --remainderPercent: calc(100% - var(--sidePercent));
23 |
24 | --innerFloat: left;
25 | --outerFloat: right;
26 |
27 | --spacerHeights-start: 0px;
28 | --spacerHeights-outer: 0px;
29 | --spacerHeights-inner: 0px;
30 | --spacerHeights-end: 0px;
31 |
32 | /*Edge Cases*/
33 | --hasInnerStartGap: 0;
34 | --hasOuterStartGap: 0;
35 | --innerStartWidth: 50%;
36 | --innerPadding: 0px;
37 | --outerStartWidth: 50%;
38 | --outerPadding: 0px;
39 | }
40 |
41 | /*Containers*/
42 | .dafRoot,
43 | .outer,
44 | .inner,
45 | .main {
46 | width: var(--contentWidth);
47 | pointer-events: none;
48 | box-sizing: content-box;
49 | }
50 |
51 | .outer, .inner, .main {
52 | position: absolute;
53 | }
54 |
55 | /*Float changes with amud*/
56 | .inner .spacer,
57 | .main .spacer.outerMid {
58 | float: var(--innerFloat);
59 | }
60 |
61 | .outer .spacer,
62 | .main .spacer.innerMid {
63 | float: var(--outerFloat);
64 | }
65 |
66 | /*Spacer widths determined by options*/
67 | .inner .spacer,
68 | .outer .spacer {
69 | width: var(--halfway);
70 | }
71 | .spacer.mid {
72 | width: var(--remainderPercent);
73 | }
74 |
75 | .main .spacer.start {
76 | width: var(--contentWidth);
77 | }
78 | .main .spacer.innerMid,
79 | .main .spacer.outerMid {
80 | width: var(--sidePercent);
81 | }
82 |
83 | /*Spacer heights determined by algorithm*/
84 | .spacer.start {
85 | height: var(--spacerHeights-start);
86 | }
87 |
88 | .spacer.end {
89 | height: var(--spacerHeights-end);
90 | }
91 |
92 | .inner .spacer.mid,
93 | .main .spacer.innerMid {
94 | height: var(--spacerHeights-inner);
95 | }
96 | .outer .spacer.mid,
97 | .main .spacer.outerMid {
98 | height: var(--spacerHeights-outer);
99 | }
100 |
101 | /*Settings to handle edge Cases*/
102 |
103 | .inner .spacer.start {
104 | width: var(--innerStartWidth);
105 | margin-left: var(--innerPadding);
106 | margin-right: var(--innerPadding);
107 | }
108 |
109 | .outer .spacer.start {
110 | width: var(--outerStartWidth);
111 | margin-left: var(--outerPadding);
112 | margin-right: var(--outerPadding);
113 | }
114 |
115 | .inner .spacer.start{
116 | margin-bottom: calc(var(--padding-vertical) * var(--hasInnerStartGap));
117 | }
118 | .outer .spacer.start {
119 | margin-bottom: calc(var(--padding-vertical) * var(--hasOuterStartGap));
120 | }
121 |
122 | .spacer.mid {
123 | clear: both;
124 | }
125 |
126 | /*Margins!*/
127 | .spacer.start,
128 | .spacer.end,
129 | .main .spacer.innerMid,
130 | .main .spacer.outerMid {
131 | margin-left: calc(0.5 * var(--padding-horizontal));
132 | margin-right: calc(0.5 * var(--padding-horizontal));
133 | }
134 |
135 | .spacer.mid,
136 | .main .text {
137 | margin-top: var(--padding-vertical);
138 | }
139 |
140 | .spacer.mid,
141 | .main .spacer.innerMid,
142 | .main .spacer.outerMid {
143 | margin-bottom: var(--padding-vertical);
144 | }
145 |
146 | /*Text*/
147 | .text {
148 | direction: var(--direction);
149 | text-align: justify;
150 | text-align-last: center;
151 | }
152 |
153 | .text span {
154 | pointer-events: auto;
155 | }
156 |
157 | .main .text {
158 | font-family: var(--fontFamily-main);
159 | font-size: var(--fontSize-main);
160 | line-height: var(--lineHeight-main);
161 | }
162 |
163 | .inner .text,
164 | .outer .text {
165 | font-size: var(--fontSize-side);
166 | line-height: var(--lineHeight-side);
167 | }
168 |
169 | .inner .text {
170 | font-family: var(--fontFamily-inner);
171 | }
172 |
173 | .outer .text {
174 | font-family: var(--fontFamily-outer);
175 | }
176 |
--------------------------------------------------------------------------------