diff --git a/README.md b/README.md new file mode 100644 index 0000000..d815941 --- /dev/null +++ b/README.md @@ -0,0 +1,44 @@ +# ASL Rulebook 2 + + +This program lets you search through the ASL Rulebook index, and jump directly to the rules you're looking for. + +Click [here](doc/features/) for more details. +
+ +With [some work](doc/extend.md), you can also: +- add rules for other modules +- have Q+A and errata automatically pop-up when you click on a rule +- include your own annotations +- include the ASOP, complete with clickable links to each rule + +*NOTE: This project integrates with my other [`asl-articles`](https://github.com/pacman-ghost/asl-articles) project, so that if an article references a rule, it becomes a clickable link that will open a browser showing that rule.* + +### Installation + +After cloning the repo, you can either: +- run it using Docker (recommended, run `./run-container.sh`) +- run it from source (requires Python 3.8.7, install the module, run `asl_rulebook2/webapp/run_server.py`) + +*NOTE: Run either command with no arguments to get help.* + +Run either command with a `--port` argument, then connect to the server in a browser e.g. + +``` ./run-container.sh --port 5020 ``` + +Then open `http://localhost:5020`. + +*NOTE: The program requires Firefox; Chrome doesn't really support a key feature it needs.* + +A few things need to be set up before the program can be used; the webapp will guide you through the process. + +*NOTE: If you are running from source, you will also need Ghostscript installed.* + +### Preparing the data files + +The first time the program is run, the MMP eASLRB PDF must be analyzed, and some key data extracted and prepared. The webapp will do this for you automatically, but in the event there are problems, [this page](doc/prepare.md) describes how to do it manually. + +### FAQ + +- Why is this project called ASL Rulebook *2*?
*Several years ago, I wrote a similar *ASL Rulebook* project that worked from a scanned copy of the ASLRB. Since it required a prepared version of the PDF, which couldn't be distributed, there was no point releasing the source code. When MMP released their official eASLRB, I updated the code to work with that, and have released it here.* +- Why doesn't the sidebar update (e.g. to show Q+A) when I click on links within the PDF itself?
*This is due to the way the program is architected. The PDF is shown in an iframe, and so the outer application can't get event notifications for things that happen inside that iframe. I might revisit this later (but it's a *lot* of work :-/).* diff --git a/asl_rulebook2/webapp/config/constants.py b/asl_rulebook2/webapp/config/constants.py index 35c818f..4a9b4cc 100644 --- a/asl_rulebook2/webapp/config/constants.py +++ b/asl_rulebook2/webapp/config/constants.py @@ -2,7 +2,7 @@ import os -APP_NAME = "ASL Rulebook" +APP_NAME = "ASL Rulebook 2" APP_VERSION = "v0.1" # nb: also update setup.py APP_DESCRIPTION = "Search engine for the ASL Rulebook." diff --git a/asl_rulebook2/webapp/static/prepare.js b/asl_rulebook2/webapp/static/prepare.js index e3076a9..d44883b 100644 --- a/asl_rulebook2/webapp/static/prepare.js +++ b/asl_rulebook2/webapp/static/prepare.js @@ -136,6 +136,9 @@ gPrepareApp.component( "upload-panel", {
You must use the offical MMP eASLRB.
A scanned copy of a printed RB will not work!
+
If there are problems here, you can try to prepare
+ your data files manually. +
`, diff --git a/asl_rulebook2/webapp/tests/fixtures/full/README.md b/asl_rulebook2/webapp/tests/fixtures/full/README.md new file mode 100644 index 0000000..bbd5a4f --- /dev/null +++ b/asl_rulebook2/webapp/tests/fixtures/full/README.md @@ -0,0 +1,57 @@ +This directory contains a set of files that demonstrate the full functionality of the program. There is only a minimal amount of content, but there's a little bit of everything, and enough to give you a feel for what the program can do. In particular, there are only a few targets defined, so most of the ruleid's will be greyed out (since the program doesn't know about them). + +There are no PDF's provided, so you won't see any real content in the content pane. However, if you have already prepared the MMP eASLRB PDF, copy it into this directory (with the name `ASL Rulebook.pdf`), and everything will automagically work. Alternatively, add `?no-content=1` to the URL when opening the webapp in your browser. + +Once you've got the hang of how things work, you can use the files here as a basis to start building the real data files. + +### Getting started + +The search engine covers: +- any index files (e.g. the `.index` files) +- any Q+A (look in the `q+a/` directory) +- any errata (look in the `errata/` directory) +- any user annotations (defined in the `annotations.json` file) +- most of the ASOP (look in the `asop/` directory) + +Take a look through those data files to get a feel for what you can search for. + +Run the program, specifying this directory as the data directory e.g. +``` +./run-container.sh --port 5020 \ + --data asl_rulebook2/webapp/tests/fixtures/full/ \ + --asop asl_rulebook2/webapp/tests/fixtures/asop/asop/ +``` + +*NOTE: You will see a few things in the UI that might look wrong; this is because this data set is used for testing the program, including error conditions and bad data.* + +### Q+A + +Search for *"encirclement"*; note how the content PDF has jumped directly to rule A7.7, and the Q+A for that rule have automatically appeared. Click on the image to zoom in. Click on the "close" icon, or press Escape to dismiss the Q+A. + +You will see the search results in the left panel, which can be filtered using the checkboxes at the top of the panel. + +If you click on the O6.7 rule link, the content pane will switch to showing the *Red Barricades* PDF (if it were there), providing seamless switching between multiple PDF files. + +### Errata + +Search for *"CCPh"*, and an errata that has been written for rule A3.8 will pop up. Note that it doesn't appear in the search results, because it doesn't contain the word *"CCPh"*. However, if you search for *"errata"*, you will get both the A3.8 index entry (CCPh), and the errata, because they both contain the word *"errata"*. + +### User annotations + +You can also add your own annotations for a specific rule, defined in the `annotations.json` file. + +Search for *"WP"* for an example. + +### ASOP + +Search for *"CC"* - the last search result will be the 8.21B ASOP step, *"DURING LOCATION's CCPh"*. Click on the titlebar to go to the ASOP entry itself. + +You can also just browse through the ASOP, by clicking on the ASOP icon in the bottom-left corner. + +### Footnotes + +Search for *"error"*. The A.2 rule will come up, and because it has an associated footnote, this will appear in a popup. Hover your mouse over the balloon if you need time to read it. + +### Chapters + +Finally, if you just want to browse through the rulebook(s), you can quickly jump to major sections withing each chapter by clicking on the *Chapters* icon in the bottom-left corner. Note how *Red Barricades* and *Kampfgruppe Scherer* have been merged in with the main rulebook, to provide a single view of all the configured rulebooks. diff --git a/doc/extend.md b/doc/extend.md new file mode 100644 index 0000000..6824183 --- /dev/null +++ b/doc/extend.md @@ -0,0 +1,41 @@ +# Adding more data + +### Adding an MMP module + +To add rules for a module already referenced by the ASLRB index (e.g. RB or BR:T), you first need to provide a scanned PDF of the rules, named `ASL Rulebook (xxx).pdf`. + +Then, write a targets file `ASL Rulebook (xxx).targets`, that describes where each rule lives within the PDF. As an example, take a look at the `ASL Rulebook.targets` file that was extracted for you. + +*NOTE: Only the page number is required, the X/Y position on the page is optional.* + +Finally, bookmarks need to be created in the PDF for each rule, so that the program can jump directly to each rule: +``` + asl_rulebook2/bin/prepare_pdf.py \ + xxx-original.pdf \ + --targets targets.json \ + --yoffset 5 \ + --output /tmp/xxx-prepared.pdf +``` +Save the prepared file as `ASL Rulebook (xxx).pdf`, and the targets file, in your data directory, and restart the server. + +Optionally, you can also provide: +- a `ASL Rulebook (xxx).chapters` file (to be able to browse the PDF in the *Chapters* panel) +- a `ASL Rulebook (xxx).footnotes` file (if the rules have any footnotes). + +To add a chapter icon and background, create files `XXX-icon.png` and `XXX-background.png`, where `XXX` is the chapter ID. For MMP modules, this is inferred from the rule ID (e.g. "O3.3" becomes "O"); for third-party modules, you can define a chapter ID by adding a `chapter_id` key to the `.chapters` files. + +### Adding a third-party module + +To add rules for a module not already referenced by the ASLRB index, the process is the same as above, but you also need to write a `.index` file. As an example, take a look at the `ASL Rulebook.index` file that was extracted for you. + +All the files should have the same base filename e.g. +- kgs.index +- kgs.pdf +- kgs.targets +- etc... + +### Adding Q+A, errata, user annoations, ASOP + +This is described [here](../asl_rulebook2/webapp/tests/fixtures/full/). + +*NOTE: If you add Q+A, there is a tool in `$/asl_rulebook2/bin/qa-helper/` to help with the process.* diff --git a/doc/features/global.css b/doc/features/global.css new file mode 100644 index 0000000..a211d8c --- /dev/null +++ b/doc/features/global.css @@ -0,0 +1,8 @@ +.ruleid { font-family: monospace ; } + +.info { + margin-top: 10px ; min-height: 25px ; + padding-left: 30px ; background: no-repeat url(info.png) ; + font-size: 90% ; font-style: italic ; color: #444 ; +} + diff --git a/doc/features/imageZoom/jquery.imageZoom.css b/doc/features/imageZoom/jquery.imageZoom.css new file mode 100644 index 0000000..dcd1e70 --- /dev/null +++ b/doc/features/imageZoom/jquery.imageZoom.css @@ -0,0 +1,48 @@ +div.jquery-image-zoom { + line-height: 0; + font-size: 0; + + z-index: 10; + + border: 5px solid #fff; + background: #eee; /* TM 25jan15: Added this to make it easier to see images with transparent backgrounds. */ + margin: -5px; + + -webkit-box-shadow: 0 0 10px rgba(0, 0, 0, 0.3); + -moz-box-shadow: 0 0 10px rgba(0, 0, 0, 0.3); + box-shadow: 0 0 10px rgba(0, 0, 0, 0.3); +} + + div.jquery-image-zoom a { + background: url(jquery.imageZoom.png) no-repeat; + + display: block; + width: 25px; + height: 25px; + + position: absolute; + left: -17px; + top: -17px; + /* IE-users are prolly used to close-link in right-hand corner */ + *left: auto; + *right: -17px; + + text-decoration: none; + text-indent: -100000px; + outline: 0; + + z-index: 11; + } + + div.jquery-image-zoom a:hover { + background-position: left -25px; + } + + div.jquery-image-zoom img, + div.jquery-image-zoom embed, + div.jquery-image-zoom object, + div.jquery-image-zoom div { + width: 100%; + height: 100%; + margin: 0; + } diff --git a/doc/features/imageZoom/jquery.imageZoom.min.js b/doc/features/imageZoom/jquery.imageZoom.min.js new file mode 100644 index 0000000..ccd572f --- /dev/null +++ b/doc/features/imageZoom/jquery.imageZoom.min.js @@ -0,0 +1 @@ +jQuery.fn.imageZoom=function(c,b){var a=c.extend({speed:200,dontFadeIn:1,hideClicked:1,imageMargin:30,className:"jquery-image-zoom",loading:"Loading..."},b);a.doubleSpeed=a.speed/4;c(document).keydown(function(d){if(d.keyCode==27){c("div.jquery-image-zoom a").click()}});return this.click(function(k){var h=c(k.target);var g=h.is("a")?h:h.parents("a");g=(g&&g.is("a")&&g.attr("href").search(/(.*)\.(jpg|jpeg|gif|png|bmp|tif|tiff)$/gi)!=-1)?g:false;var i=(g&&g.find("img").length)?g.find("img"):false;c("div.jquery-image-zoom a").click();if(g){g.oldText=g.text();g.setLoadingImg=function(){if(i){i.css({opacity:"0.5"})}else{g.text(a.loading)}};g.setNotLoadingImg=function(){if(i){i.css({opacity:"1"})}else{g.text(g.oldText)}};var d=g.attr("href");if(c("div."+a.className+' img[src="'+d+'"]').length){return false}var j=function(l){g.setNotLoadingImg();var u=i?i:g;var q=i?a.hideClicked:0;var p=u.offset();var n={width:u.outerWidth(),height:u.outerHeight(),left:p.left,top:p.top};var o=c('
').css("position","absolute").appendTo(document.body);var m={width:l.width,height:l.height};var s={width:c(window).width(),height:c(window).height()};if(m.width>(s.width-a.imageMargin*2)){var r=s.width-a.imageMargin*2;m.height=(r/m.width)*m.height;m.width=r}if(m.height>(s.height-a.imageMargin*2)){var t=s.height-a.imageMargin*2;m.width=(t/m.height)*m.width;m.height=t}m.left=(s.width-m.width)/2+c(window).scrollLeft();m.top=(s.height-m.height)/2+c(window).scrollTop();var e=c('Close').appendTo(o).hide();if(q){g.css("visibility","hidden")}o.addClass(a.className).css(n).animate(m,a.speed,function(){e.fadeIn(a.doubleSpeed)});var v=function(){e.fadeOut(a.doubleSpeed,function(){o.animate(n,a.speed,function(){g.css("visibility","visible");o.remove()})});return false};o.click(v);e.click(v)};var f=new Image();f.src=d;if(f.complete){j(f)}else{g.setLoadingImg();f.onload=function(){j(f)}}return false}})}; \ No newline at end of file diff --git a/doc/features/imageZoom/jquery.imageZoom.png b/doc/features/imageZoom/jquery.imageZoom.png new file mode 100644 index 0000000..2f5a381 Binary files /dev/null and b/doc/features/imageZoom/jquery.imageZoom.png differ diff --git a/doc/features/images/.gitignore b/doc/features/images/.gitignore new file mode 100644 index 0000000..e9bb3ec --- /dev/null +++ b/doc/features/images/.gitignore @@ -0,0 +1 @@ +/snags/ diff --git a/doc/features/images/asl-rulebook2.png b/doc/features/images/asl-rulebook2.png new file mode 100644 index 0000000..b0ae3a8 Binary files /dev/null and b/doc/features/images/asl-rulebook2.png differ diff --git a/doc/features/images/asl-rulebook2.small.png b/doc/features/images/asl-rulebook2.small.png new file mode 100644 index 0000000..ccab410 Binary files /dev/null and b/doc/features/images/asl-rulebook2.small.png differ diff --git a/doc/features/images/asop-sr.png b/doc/features/images/asop-sr.png new file mode 100644 index 0000000..0cd63a1 Binary files /dev/null and b/doc/features/images/asop-sr.png differ diff --git a/doc/features/images/asop.png b/doc/features/images/asop.png new file mode 100644 index 0000000..f4c4523 Binary files /dev/null and b/doc/features/images/asop.png differ diff --git a/doc/features/images/chapters-extended.png b/doc/features/images/chapters-extended.png new file mode 100644 index 0000000..281e702 Binary files /dev/null and b/doc/features/images/chapters-extended.png differ diff --git a/doc/features/images/chapters.png b/doc/features/images/chapters.png new file mode 100644 index 0000000..a04b324 Binary files /dev/null and b/doc/features/images/chapters.png differ diff --git a/doc/features/images/ruleinfo-encircled.png b/doc/features/images/ruleinfo-encircled.png new file mode 100644 index 0000000..a4cfa69 Binary files /dev/null and b/doc/features/images/ruleinfo-encircled.png differ diff --git a/doc/features/images/search-air-support.png b/doc/features/images/search-air-support.png new file mode 100644 index 0000000..f694bbe Binary files /dev/null and b/doc/features/images/search-air-support.png differ diff --git a/doc/features/images/search-cellar.png b/doc/features/images/search-cellar.png new file mode 100644 index 0000000..0abe39d Binary files /dev/null and b/doc/features/images/search-cellar.png differ diff --git a/doc/features/images/search-encircled.png b/doc/features/images/search-encircled.png new file mode 100644 index 0000000..9f28794 Binary files /dev/null and b/doc/features/images/search-encircled.png differ diff --git a/doc/features/images/search-heat.png b/doc/features/images/search-heat.png new file mode 100644 index 0000000..33b4b80 Binary files /dev/null and b/doc/features/images/search-heat.png differ diff --git a/doc/features/images/user-anno.png b/doc/features/images/user-anno.png new file mode 100644 index 0000000..74f7e89 Binary files /dev/null and b/doc/features/images/user-anno.png differ diff --git a/doc/features/index.html b/doc/features/index.html new file mode 100644 index 0000000..7f8f74b --- /dev/null +++ b/doc/features/index.html @@ -0,0 +1,77 @@ + + + + + + + ASL Rulebook 2 features + + + + + + +

+

Basic features

+

Out-of-the-box, this program gives you full-text search over the ASLRB index. +

It will jump to the exact position in the eASLRB PDF for a rule when you click on a search result. +

And if the rule has any associated footnotes, these will be shown in a popup. + +

You can also browse the ASLRB by chapter:
+ + +

+ + +

+

Adding rules for other modules

+

Once you've got the program up and running, you can then think about extending it. It's a lot of work, but the results are insanely cool! +

To the right, I searched for "cellar", and the program has found results from Red Barricades. The rules for this are referenced in the ASLRB index, but the content is not yet in the MMP eASLRB. +

However, I added a PDF scan of the rules, plus information about where each rule is within that PDF (a "targets file"), and so when I click on the O3.3 search result, it seamlessly opens the Red Barricades PDF and jumps to that rule. +

+ +

+You can also include third-party modules that are not referenced in the ASLRB index, in this case, LFT's Kampfgruppe Scherer. I added an index for it, which is then also searched. As with Red Barricades, I also added a PDF scan of the rules, and a targets file that specifies the location of each rule, and clicking on the KGS CG9 search result takes me directly to that rule in the KGS PDF. +
+ +

I also added information about chapters in the PDF's, so that I can browse through them in the usual way:
+ + +

+ + +

+

Showing Q+A and errata

+Q+A and errata can also be included. This is a lot of work, but the results are amazing. If you click on a rule that has Q+A and/or errata associated with it, they will be shown, alongside the rule you're looking for. +

Here, I've searched for "encircled", and the program has automatically shown Q+A and errata for rule A7.7. +

Note that this errata is actually obsolete, since it's already been incorporated into the current MMP eASLRB, but it's shown here as an example.
+
+ +

+Going back to the search results, you can see these Q+A entries included in there. +

Note the checkboxes underneath the search box, that let you filter the results by type. +
+ + +

+

User annotations

+You can also add your own notes to the search engine. +

Here, I've added a note about what Majority Squad Type means, and a link back to the Game Squad post that talks about it. +
+ + +

+

Advanced Sequence Of Play

+Finally, the ASOP can also be included, with clickable links to each of the referenced rules. +

ASOP entries are also included in search results:
+ + + + + + + + + diff --git a/doc/features/info.png b/doc/features/info.png new file mode 100644 index 0000000..ef9a995 Binary files /dev/null and b/doc/features/info.png differ diff --git a/doc/features/jquery-3.6.0.min.js b/doc/features/jquery-3.6.0.min.js new file mode 100644 index 0000000..c4c6022 --- /dev/null +++ b/doc/features/jquery-3.6.0.min.js @@ -0,0 +1,2 @@ +/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="

",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0 { + // make images zoomable + makeImagesZoomable( $("body") ) ; +} ) ; + +// -------------------------------------------------------------------- + +function makeImagesZoomable( $elem ) +{ + // look for images that have been marked as zoomable, and make it so + $elem.find( "img.imageZoom" ).each( function() { + $(this).wrap( $( "", { + class: "imageZoom", + href: $(this).attr( "src" ), + title: "Click to zoom", + onFocus: "javascript:this.blur()" + } ) ) ; + } ) ; + $elem.find( "img.imageZoom" ).imageZoom( $ ) ; +} diff --git a/doc/prepare.md b/doc/prepare.md new file mode 100644 index 0000000..412bc4b --- /dev/null +++ b/doc/prepare.md @@ -0,0 +1,59 @@ +# Preparing the data files + +The first time the program is run, the MMP eASLRB PDF must be analyzed (to extract some key data), and prepared. The webapp will do this for you automatically, but since there are different versions of the eASLRB (and everyone gets a different file anyway, because of the watermarks), it's possible that the automatic process might fail. + +In this case, you can try to prepare the data files manually, which gives you a chance to fix any problems. + +The steps below assume that you've created a directory `/tmp/prepared/` to store the prepared files, and `$EASLRB` refers to your copy of the MMP eASLRB PDF file. + +*NOTE: If you are running the program using Docker, you will need to perform these steps inside the container.* + +### Extract from the eASLRB PDF + +The first step is to extract the information we need from the eASLRB PDF. +``` + asl_rulebook2/extract/all.py $EASLRB \ + --format json \ + --save-index /tmp/prepared/ASL\ Rulebook.index \ + --save-targets /tmp/prepared/ASL\ Rulebook.targets \ + --save-chapters /tmp/prepared/ASL\ Rulebook.chapters \ + --save-footnotes /tmp/prepared/ASL\ Rulebook.footnotes \ + --progress +``` +This extracts the information we need, and saves it in the 4 data files. + +### Prepare the PDF + +Next, we need to prepare the eASLRB PDF, namely create bookmarks for each rule, so that the webapp can jump directly to each one: +``` + asl_rulebook2/bin/prepare_pdf.py \ + $EASLRB \ + --targets /tmp/prepared/ASL\ Rulebook.targets \ + --yoffset 5 \ + --output /tmp/prepared.pdf \ + --compression ebook \ + --progress +``` +We also take the opportunity to compress the PDF. + +### Fixup the PDF + +Finally, we need to fixup some issues in the PDF: +``` + asl_rulebook2/bin/fixup_mmp_pdf.py \ + /tmp/prepared.pdf \ + --rotate \ + --optimize-web \ + --output /tmp/prepared/ASL\ Rulebook.pdf \ + --progress +``` +This fixes the zoom problem when clicking on links within the PDF, and rotates any landscape pages. + +### Using the prepared files + +You should now have 5 files (the 4 extracted data files, plus the fixed-up PDF), which can be passed in to the program e.g. +``` + ./run-container.sh \ + --port 5020 \ + --data /tmp/prepared/ +```