Updated for v2.01 of the MMP eASLRB.

master
Pacman Ghost 2 years ago
parent 851d6e94cb
commit 87ba93773e
  1. 6
      asl_rulebook2/bin/fixup_mmp_pdf.py
  2. 144
      asl_rulebook2/extract/content.py
  3. 6
      asl_rulebook2/extract/data/chapter-fixups.json
  4. 85
      asl_rulebook2/extract/data/footnote-fixups.json
  5. 7
      asl_rulebook2/extract/data/index-fixups.json
  6. 103
      asl_rulebook2/extract/data/target-fixups.json
  7. 330
      asl_rulebook2/extract/data/vo-note-fixups.json
  8. 2
      asl_rulebook2/extract/index.py
  9. 4
      asl_rulebook2/pdf.py
  10. 2
      asl_rulebook2/webapp/static/prepare.js

@ -91,6 +91,12 @@ def fixup_mmp_pdf( fname, output_fname, fix_zoom, optimize_web, rotate, log=None
annot.Dest = make_page_destination( pdf, page_no, "XYZ", top=page_height )
log_msg( None, "" )
# FUDGE! v2.01 of the MMP eASLRB PDF had a bodgy p104 (A62) that is a little wider than
# the rest of the pages (dumping the page dimensions using pikepdf didn't show anything unusual,
# but it was rendering differently in Firefox :-/), which was causing the h-scrollbar to appear.
# We hack the page width here to bring it in line with the others.
pdf.pages[ 104-1 ].MediaBox = [ 0, 0, 565, 792 ]
# save the updated PDF
log_msg( "progress", "Saving the fixed-up PDF..." )
# NOTE: Setting a blank password will encrypt the file, but doesn't require the user

@ -22,25 +22,36 @@ from asl_rulebook2.utils import parse_page_numbers, fixup_text, append_text, rem
_DISABLE_SORT_ITEMS = [
"B40", # nb: to detect B31.1 NARROW STREET
"A16",
"A54", "A55", "A56",
"A58","A59","A60", # Chapter A footnotes (nb: page A61 is a mess wrt element order :-/)
"B1",
"B45", "B46", # Chapter B footnotes
"C25", "C26", # Chapter C footnotes
"D27", # Chapter D footnotes
"E28", "E29", "E30", # Chapter E footnotes
"F20", "F21", # Chapter F footnotes
"F2", "F6", "F7", "F8", "F9", "F10", "F11", "F16", "F17",
"F18", "F19", # Chapter F footnotes
"G48", "G49", "G50", # Chapter G footnotes
"H9", # Chapter H footnotes
429,431,432,433,434,435, # Italian vehicle notes
436,437,438,439, # Italian ordnance notes
# Chapter H vehicles/ordnace:
359, 360,
363, 374,
376, 383, 387, 388, 390, 393,
408, 417, 426, 429,
434, 436, 438,
449, 452, 453,
467, 468, 469,
486, 492,
529, 530, 531, 532,
547, 549, 589,
]
_DEFAULT_ARGS = {
"chapter-a": "42-102", "chapter-b": "109-154", "chapter-c": "158-183", "chapter-d": "187-213",
"chapter-e": "216-245", "chapter-f": "247-267", "chapter-g": "270-319", "chapter-h": "322-324,326-330",
"chapter-j": "593",
"chapter-w": "647-664",
"content_vp_left": 0, "content_vp_right": 565, "content_vp_top": 715, "content_vp_bottom": 28, # viewport
"chapter-a": "43-104", "chapter-b": "111-156", "chapter-c": "161-186", "chapter-d": "191-217",
"chapter-e": "221-250", "chapter-f": "253-271", "chapter-g": "275-324", "chapter-h": "327-329,331-335",
"chapter-j": "599",
"chapter-w": "653-670",
"content_vp_left": 0, "content_vp_right": 600, "content_vp_top": 715, "content_vp_bottom": 28, # viewport
"disable-sort-items": ",".join( str(si) for si in _DISABLE_SORT_ITEMS )
}
@ -48,35 +59,35 @@ _DEFAULT_ARGS = {
# - the order of the nationality + V/O types
# - the page numbers themselves (so that they get parsed)
_VO_NOTE_SECTIONS = [
[ "german", "vehicles", "330,332,334-343", True ],
[ "german", "ordnance", "344-348", True ],
[ "russian", "vehicles", "348,350-355", True ],
[ "russian", "ordnance", "356-358", True ],
[ "russian", "vehicles", "362,364-368", False ],
[ "russian", "ordnance", "369", False ],
[ "american", "vehicles", "371,373-383", True ],
[ "american", "ordnance", "385-389", True ],
[ "british", "vehicles", "395,398-417", True ],
[ "british", "ordnance", "419-423", True ],
[ "italian", "vehicles", "429,431-435", True ],
[ "italian", "ordnance", "436-439", True ],
[ "japanese", "vehicles", "443-448", True ],
[ "japanese", "ordnance", "448-452", True ],
[ "chinese", "vehicles", "456-459", True ],
[ "chinese", "ordnance", "459-463", True ],
[ "landing-craft", "vehicles", "467-468", True ],
[ "french", "vehicles", "470,472-480", True ],
[ "french", "ordnance", "482-487", True ],
[ "allied-minor", "vehicles", "492-493,495-500", True ],
[ "allied-minor", "ordnance", "501-504", True ],
[ "axis-minor", "vehicles", "506,508-515", True ],
[ "axis-minor", "ordnance", "516,518-527", True ],
[ "finnish", "vehicles", "536,538-541", True ],
[ "finnish", "ordnance", "543,545-549", True ],
[ "un-forces", "vehicles", "554,556-565", True ],
[ "un-forces", "ordnance", "567-570", True ],
[ "communist-forces", "vehicles", "580", True ],
[ "communist-forces", "ordnance", "581-585", True ],
[ "german", "vehicles", "335,337,339-348", True ],
[ "german", "ordnance", "349-353", True ],
[ "russian", "vehicles", "353,355-360", True ],
[ "russian", "ordnance", "361-363", True ],
[ "russian", "vehicles", "367,369-373", False ],
[ "russian", "ordnance", "374", False ],
[ "american", "vehicles", "376,378-388", True ],
[ "american", "ordnance", "390-394", True ],
[ "british", "vehicles", "400,403-422", True ],
[ "british", "ordnance", "424-428", True ],
[ "italian", "vehicles", "434,436-440", True ],
[ "italian", "ordnance", "441-444", True ],
[ "japanese", "vehicles", "449-454", True ],
[ "japanese", "ordnance", "454-458", True ],
[ "chinese", "vehicles", "462-465", True ],
[ "chinese", "ordnance", "465-469", True ],
[ "landing-craft", "vehicles", "473-474", True ],
[ "french", "vehicles", "476,478-486", True ],
[ "french", "ordnance", "488-493", True ],
[ "allied-minor", "vehicles", "498-499,501-506", True ],
[ "allied-minor", "ordnance", "507-510", True ],
[ "axis-minor", "vehicles", "512,514-521", True ],
[ "axis-minor", "ordnance", "522,524-533", True ],
[ "finnish", "vehicles", "542,544-547", True ],
[ "finnish", "ordnance", "549,551-555", True ],
[ "un-forces", "vehicles", "560,562-571", True ],
[ "un-forces", "ordnance", "573-576", True ],
[ "communist-forces", "vehicles", "586", True ],
[ "communist-forces", "ordnance", "587-591", True ],
]
# ---------------------------------------------------------------------
@ -103,7 +114,7 @@ class ExtractContent( ExtractBase ):
self._footnote_fixups = load_fixup( "footnote-fixups.json" )
self._vo_note_fixups = load_fixup( "vo-note-fixups.json" )
def extract_content( self, pdf ):
def extract_content( self, pdf ): #pylint: disable=too-many-branches
"""Extract content from the MMP eASLRB."""
# figure out which pages to process
@ -169,7 +180,10 @@ class ExtractContent( ExtractBase ):
def elem_filter( elem ):
return isinstance( elem, LTChar )
sort_elems = self._curr_pageid not in disable_sort_items and str(page_no) not in disable_sort_items
for _, elem in PageElemIterator( lt_page, elem_filter=elem_filter, sort_elems=sort_elems ):
centre_adjust = 35 if self._curr_footnote is not None and self._curr_chapter != "W" else 0
for _, elem in PageElemIterator( lt_page, centre_adjust=centre_adjust,
elem_filter=elem_filter, sort_elems=sort_elems
):
# skip problematic elements
if elem.fontname == "OYULKV+MyriadPro-Regular":
@ -238,6 +252,17 @@ class ExtractContent( ExtractBase ):
# loop back to process the next element
self._prev_elem = elem
# check for extra targets that need to be added in
extra_targets = self._target_fixups.get( self._curr_pageid, {} ).pop( "extras", [] )
if extra_targets:
if not self._target_fixups[ self._curr_pageid ]:
del self._target_fixups[ self._curr_pageid ]
for extra_target in extra_targets:
self.targets[ extra_target["ruleid"] ] = {
"caption": extra_target.get("caption",""),
"page_no": page_no, "pos": extra_target["pos"]
}
# add the last caption/footnote (if they haven't already been done)
self._save_footnote()
if curr_caption:
@ -277,7 +302,7 @@ class ExtractContent( ExtractBase ):
fixup = self._target_fixups.get( self._curr_pageid, {} ).get( caption_text )
if fixup:
# yup - make it so
fixup[ "instances" ] = fixup.get("instances",1) - 1
fixup[ "instances" ] = fixup.get( "instances", 1 ) - 1
if fixup["instances"] <= 0:
self._target_fixups[ self._curr_pageid ].pop( caption_text )
if not self._target_fixups[ self._curr_pageid ]:
@ -325,7 +350,14 @@ class ExtractContent( ExtractBase ):
"""Process an element while we're parsing footnotes."""
# check if we've found the start of a new footnote
if self._is_bold( elem ):
if elem.get_text().isdigit() and self._is_start_of_line( elem, lt_page ):
# FUDGE! The new Chapter F has things like "13. 7.1 SAND", which fooled the code into thinking
# that the "7" was the start of a new footnote (because it's a digit, and near the start
# of the line :-/), so we check for that case here.
def check():
if self._curr_chapter != "F" or not self._curr_footnote:
return False
return any( self._curr_footnote[0] == "{}. ".format(i) for i in range(10,15+1) )
if elem.get_text().isdigit() and self._is_start_of_line( elem, lt_page ) and not check():
# yup - save the current footnote, start collecting the new one
self._save_footnote()
elem_pos = ( elem.x0, elem.y1 )
@ -460,7 +492,7 @@ class ExtractContent( ExtractBase ):
# check for the credits at the end of the Chapter F footnotes
if self._curr_chapter == "F":
pos = content.find( "WEST OF ALAMEIN CREDITS" )
pos = content.find( "HOLLOW LEGIONS CREDITS" )
if pos > 0:
content = content[:pos]
# check for the start of the vehicle notes at the end of the Chapter H footnotes
@ -477,6 +509,21 @@ class ExtractContent( ExtractBase ):
"raw_content": orig_content
} )
self._curr_footnote = None
split = self._footnote_fixups.get( self._curr_chapter, {} ).pop( "split:"+footnote_id, None )
if split:
content = self._footnotes[self._curr_chapter][-1]["content"]
pos = content.find( split["split_text"] )
if pos < 0:
self.log_msg( "warning", "Can't find footnote split: {}", split["split_text"] )
else:
new_content = content[ pos + len( split["split_text"] ) : ]
self._footnotes[self._curr_chapter][-1]["content"] = content[:pos].strip()
self._footnotes[ self._curr_chapter ].append( {
"footnote_id": split["new_footnote_id"],
"captions": split["new_captions"],
"content": new_content.strip(),
"raw_content": None
} )
def _save_vo_note( self, caption, page_no, page_pos ):
"""Save an extracted vehicle/ordnance note."""
@ -494,7 +541,9 @@ class ExtractContent( ExtractBase ):
if not self._vo_note_fixups["skips"]:
del self._vo_note_fixups["skips"]
return
if caption.isdigit() and page_no not in (354, 417):
if caption.isdigit():
return
if not any( ch.isalpha() for ch in caption ):
return
def apply_fixups( vo_note_id, caption ):
@ -510,6 +559,9 @@ class ExtractContent( ExtractBase ):
vo_note_id = fixup["new_vo_note_id"]
if "new_caption" in fixup:
caption = fixup["new_caption"]
if "new_page_pos" in fixup:
nonlocal page_pos
page_pos = fixup["new_page_pos"]
return vo_note_id, caption
def cleanup_fixups( nat, vo_type ):
@ -567,7 +619,11 @@ class ExtractContent( ExtractBase ):
elif base_note_id == prev_base_note_id and "." in vo_note_id:
pass # nb: this is to allow things like "9.1" following "9"
else:
return # nb: we got some junk that can be ignored
# NOTE: We used to return here, which ignored a lot of junk, but it has the unfortunate
# side-effect of, if we missed an entry, then all entries after that would also be missed.
# With v2.01 of the eASLRB, there were changes to the layout that weren't really fixable
# using the fixup data files, so we bit the bullet and manually ignore the junk :-/
pass
# save the V/O note
self._vo_notes[ nat ][ vo_type ].append( {

@ -25,7 +25,11 @@
"replace": {
"B32": [ "RAILROADS10", "Railroads" ],
"B34": [ "TOWERS18", "Towers" ],
"B36": [ "PREPARED FIRE ZONE2", "Prepared Fire Zone" ]
"B36": [ "PREPARED FIRE ZONE2", "Prepared Fire Zone" ],
"F1": [ "OPEN GROUND", "Open Ground" ],
"F2": [ "SCRUB", "Scrub" ],
"F4": [ "DEIRS", "Deirs" ],
"F5": [ "WADIS", "Wadis" ]
}
}

@ -2,20 +2,14 @@
"A": {
"10A": [
[ "OneHalfFP", "One-Half FP" ],
[ "firstappearedintheASLAnnual'89.(In1998,bothwerereprintedin Classic ASL.)", "first appeared in the ASL Annual '89. (In 1998, both were reprinted in Classic ASL.)" ],
[ "One of the several criticisms", "<p> One of the several criticisms" ]
],
"12": [ [ "TEMto", "TEM to" ] ],
"14": [
[ "bipodmounted", "bipod-mounted" ],
[ "volume o f fire", "volume of fire" ]
],
"17": [ [ "adistinct", "a distinct" ] ],
"19" : [ [ "wellsited", "well-sited" ] ],
"32": [ [ "HWunits", "HW units" ] ],
"33": [ [ "multiLocation", "multi-Location" ] ],
"10A": [ [ "One of the several criticisms", "<p> One of the several criticisms" ] ],
"14": [ [ "bipodmounted", "bipod-mounted" ] ],
"19": [ [ "wellsited", "well-sited" ] ],
"split:31A": {
"split_text": "31B. 25.212 RUSSIAN EARLY WAR DOCTRINE:",
"new_footnote_id": "31B",
"new_captions": [ [ "A25.212", "RUSSIAN EARLY WAR DOCTRINE" ] ]
},
"35": [ [ "The original printing", "<p> The original printing" ] ],
"37": [
[ "- Winter War (vs Soviet Union) 30 November 1939 - 13 March 1940- Continuation War (vs Soviet Union) 25 June 1941 - 4 September 1944- Lapland War (vs Germany) 15 September 1944 - 27 April 1945", " <ul> <li> <b>Winter War</b> (vs Soviet Union) 30 November 1939 - 13 March 1940 <li> <b>Continuation War</b> (vs Soviet Union) 25 June 1941 - 4 September 1944 <li> <b>Lapland War</b> (vs Germany) 15 Se ptember 1944 - 27 April 1945 </ul>" ]
@ -26,16 +20,12 @@
[ "Slovakia: Urged on", "<p> <b>Slovakia</b>: Urged on" ],
[ "German-Croatian units in Russia:", " <p> <b>German-Croatian units in Russia</b>:" ],
[ "Italian-Croatian units in Russia:", " <p> <b>Italian-Croatian units in Russia</b>:" ],
[ "existing GermanCroatian units", "existing German-Croatian units" ],
[ "Croatian units in Yugoslavia:", " <p> <b>Croatian units in Yugoslavia</b>:" ],
[ "CroatianArmyunitswereengagedprimarilyinanti-partisanactivities,fightingmostly", "Croatian Army units were engaged primarily in anti-partisan activities, fighting mostly" ],
[ "Bulgaria: Bulgaria", "<p> <b>Bulgaria</b>: Bulgaria" ],
[ "WhiletheriflecompanydidnothaveaninherentHeavyWeapons(HW)platoon,it", "While the rifle company did not have an inherent Heavy Weapons (HW) platoon, it"]
[ "Bulgaria: Bulgaria", "<p> <b>Bulgaria</b>: Bulgaria" ]
],
"39": [ [ "generallyapply", "generally apply" ] ],
"41": [ [ "ViceAdmiral", "Vice-Admiral" ] ],
"43": [
[ "ALLIEDMINORS", "ALLIED MINORS" ],
[ "BARrather", "BAR rather" ]
"42": [
[ "twoman", "two-man" ]
]
},
@ -107,51 +97,14 @@
"F": {
"split:7": {
"split_text": "8. 5.1 WADIS:",
"new_footnote_id": "8",
"new_captions": [ [ "F5.1", "WADIS" ] ]
},
"12": [ [ "non- entrenched", "non-entrenched" ] ],
"19": [
[ "Inthewinternight,thenear-freezingtemperaturecauseddewtoform.", "In the winter night, the near-freezing temperature caused dew to form. " ],
[ "Thenextmorningathickmistoftenformedasthesun evaporateditagain.", "The next morning a thick mist often formed as the sun evaporated it again. " ],
[ "Thiscouldhappeneveninthesummertimeundertheproperenvironmentalconditions,", "This could happen even in the summertime under the proper environmental conditions, " ],
[ "butsincethiswasamuchlessfrequentoccurrenceithasbeen ignored.", "but since this was a much less frequent occurrence it has been ignored." ]
],
"21": [
[ "Playerswillprobablyfinditmoreconvenienttoinstead", "Players will probably find it more convenient to instead" ],
[ "addathird,different-coloreddietothisTH/IFTDR,", "add a third, different-colored die to this TH/IFT DR, " ],
[ "usingittodeterminetheDust DRM.", "using it to determine the Dust DRM." ],
[ "Thefamiliarterm\"subsequentdr\"wasusedintherulebecauseitobviates theneed", "The familiar term \"subsequent dr\" was used in the rule because it obviates the need" ],
[ "a\"new\"concept", "a \"new\" concept" ],
[ "thatof rolling athird diesimultaneously", "that of rolling a third die simultaneously" ]
],
"22": [
[ "theDustcounter\"follows\"thevehicleasit movesfromhex to hex", "the Dust counter \"follows\" the vehicle as it moves from hex to hex" ],
[ "itexpends", "it expends " ],
[ "two MPeach timeitdoesso", " two MP each time it does so" ]
],
"23": [
[ "Anotherwind-relatedaspectoftheNorthAfricanenvironmentisthedesertsandstorm,", "Another wind-related aspect of the North African environment is the desert sandstorm, " ],
[ "orkhamsininArabic.", "or khamsin in Arabic. " ],
[ "ChapterFincludesnospecial rulesforitbecause,", "Chapter F includes no special rules for it because, " ],
[ "withvisibilitycutbythestormtoaslittleasthreeyards,", "with visibility cut by the storm to as little as three yards, " ],
[ "allactivitiesgenerallywerereducedtoseekingcoverfromthesandblastingwindandchoking dust.", "all activities generally were reduced to seeking cover from the sandblasting wind and choking dust. " ],
[ "However,thegamedoesnotignorethepossibilityofakhamsin'soccurrence.", "However, the game does not ignore the possibility of a khamsin's occurrence. " ],
[ "The propercombinationofWeather,EC,WindandGustsinaDYOscenariocancreateits effects,", "The proper combination of Weather, EC, Wind and Gusts in a DYO scenario can create its effects, " ],
[ "andtheprobabilityofitsoccurrenceisgreatestinascenariosetinspringor summer", "and the probability of its occurrence is greatest in a scenario set in spring or summer" ],
[ "thetimewhen khamsinsoccurred mostfrequently.", "the time when khamsins occurred most frequently." ]
],
"24": [
[ "Thisoverlay isused in aHOLLOW LEGIONS scenario.", "This overlay is used in a HOLLOW LEGIONS scenario." ]
],
"25": [
[ "ThefamousNorthAfricanescarpmentsaresimilarto cliffs,", "The famous North African escarpments are similar to cliffs, " ],
[ "butwithlesssteep(andveryeroded)slopes.", "but with less steep (and very eroded) slopes. " ],
[ "Somearesixhundredfeethigh", "Some are six hundred feet high" ],
[ "thoughgenerallytheirheightsrangefromonehundredtotwohundredfeet.", "though generally their heights range from one hundred to two hundred feet. " ],
[ "Theirsignificanceinthedesertwarlaymainlyinthattheywerecommandingheights,", "Their significance in the desert war lay mainly in that they were commanding heights, " ] ,
[ "defensivepositionsforinfantry,", "defensive positions for infantry, " ],
[ "andgreatlyrestrictedvehicularmovementacrossthem", "and greatly restricted vehicular movement across them" ],
[ "Hencetheywereoftenthesceneofheavyfighting,", "Hence they were often the scene of heavy fighting, " ],
[ "especiallywherecrossedbya road", "especially where crossed by a road" ]
]
"19": [ [ "nearfreezing", "near-freezing" ] ]
},
"G": {

@ -66,6 +66,13 @@
"new_content": "(Any fire attack requiring a LOS from the firer which does not use Indirect Fire): C.1, C9.1 [Intervening Units: A6.6] [LC: G12.61-.62, G12.671]"
},
"Dispersed Smoke": {
"replace": [
[ "SmokeGrenadesNA", "Smoke Grenades NA" ],
[ "VehicularSmoke", "Vehicular Smoke" ]
]
},
"Dogfight": {
"old_content": "(AerialCombat):E7.22",
"new_content": "(Aerial Combat): E7.22"

@ -108,18 +108,10 @@
}
},
"A54": {
"25.53 FREEFRENCH:": {
"new_ruleid": "A25.53",
"new_caption": "FREE FRENCH"
}
},
"A55": {
"26.VICTORYCONDITIONS": {
"new_ruleid": "A26",
"new_caption": "VICTORY CONDITIONS"
}
"extras": [
{ "ruleid": "A25.71", "caption": "LEADERS", "pos": [172,714] }
]
},
"B1": {
@ -132,7 +124,10 @@
"T10": { "new_ruleid": null },
"W5": { "new_ruleid": null },
"V5": { "new_ruleid": null },
"X6": { "new_ruleid": null }
"X6": { "new_ruleid": null },
"Y6": { "new_ruleid": null },
"Y7": { "new_ruleid": null },
"Y10": { "new_ruleid": null }
},
"B4": {
@ -348,10 +343,86 @@
"3)": { "new_ruleid": null }
},
"F18": {
"D3": { "new_ruleid": null },
"W1": { "new_ruleid": null },
"H4": { "new_ruleid": null }
"F1": {
"8½ 1 3 3 5 1 7": { "new_ruleid": null },
"1 1": { "new_ruleid": null },
"17": { "new_ruleid": null }
},
"F2": {
"1. OPEN GROUND 1.1": {
"new_ruleid": "F1",
"new_caption": "OPEN GROUND"
},
"2. SCRUB 2.1": {
"new_ruleid": "F2",
"new_caption": "SCRUB"
},
"extras": [
{ "ruleid": "F1.1", "pos": [18,330] },
{ "ruleid": "F2.1", "pos": [18,173] },
{ "ruleid": "F3", "caption": "HAMMADA", "pos": [294,592] },
{ "ruleid": "F3.1", "pos": [294,582] }
]
},
"F3": {
"4. DEIRS 7 4.1": {
"new_ruleid": "F4",
"new_caption": "DEIRS"
},
"5. WADIS 8 5.1": {
"new_ruleid": "F5",
"new_caption": "WADIS"
},
"extras": [
{ "ruleid": "F4.1", "pos": [66,109] },
{ "ruleid": "F5.1", "pos": [342,166] }
]
},
"F6": {
"3 TEM:": {
"new_ruleid": "F5.423",
"new_caption": "TEM"
}
},
"F7": {
"extras": [
{ "ruleid": "F6", "caption": "HILLOCKS", "pos": [66,276] }
]
},
"F8": {
"extras": [
{ "ruleid": "F7", "caption": "SAND", "pos": [294,531] },
{ "ruleid": "F7.1", "pos": [294,520] }
]
},
"F12": {
"11.4": {
"new_ruleid": "F11.4",
"new_caption": "ENVIRONMENTAL CONDITIONS"
},
"18": { "new_ruleid": null },
"extras": [
{ "ruleid": "F11.3", "caption": "TIME OF DAY", "pos": [294,529] }
]
},
"F14": {
"20 11.7 DUST:": {
"new_ruleid": "F11.7",
"new_caption": "DUST"
},
"21": { "new_ruleid": null },
"22": { "new_ruleid": null }
},
"F15": {
"23": { "new_ruleid": null }
},
"G30": {

@ -1,23 +1,27 @@
{
"skips": {
"382": [ "1, 3" ],
"429": [ "^1,660,", "^1and Fiat 3000", "^9/43 armistice", "^4/41 (.9)" ],
"431": [ "^1, for East Africa", "^9/42 (1.4)," ],
"432": [ "^1 (l.2),", "^1. Sources vary" ],
"434": [ "1-", "^1.5 for 11/41-6/42," ],
"438": [ "^1/41-5/43" ],
"439": [ "1 (1", "^1/43 ( 1.2),", "^1/43 (1.3),", "^1/42-5/43." ],
"492": [ "1B11CE/FPNA", "1B11CE/FPNA" ],
"493": [ "1T", "1B" ],
"496": [ "1B" ],
"501": [ "1h-d" ],
"502": [ "1s5", "1s5" ],
"503": [ "1AP5", "1s6" ],
"504": [ "^1.3)" ],
"514": [ "1.4 for 45" ],
"556": [ "1#" ],
"560": [ "1, 3" ]
"342": [ "5.1 GSW 39H(f) PaK:" ],
"345": [ "4 FP", "4 FP", "6 FP", "4 FP", "4 FP", "4 FP", "6 FP", "4 FP" ],
"355": [ "26S M37/39:" ],
"422": [ "30-cwt Lorry:", "3-Ton Lorry:" ],
"463": [ "243M2" ],
"466": [ "^50mm RM obr. 38," ],
"468": [ "20/65, & 2cm FlaK 30:" ],
"485": [ "4, M4A1, & M4A2 Medium Tanks:" ],
"498": [ "11:Stall", "11:Stall", "1B11CE/FPNA", "1B11CE/FPNA", "2x" ],
"499": [ "1T", "1B", "2B11B11" ],
"501": [ "4PP", "5PPcs 5", "5PP" ],
"502": [ "1B" ],
"504": [ "2/2CS 6" ],
"505": [ "9PPcs 2", "9PP*" ],
"506": [ "29PP", "21PP", "9PP", "9PP" ],
"507": [ "4M", "1h-d", "11M" ],
"508": [ "8M", "12M", "8M", "1s5", "11M", "12M" ],
"509": [ "3/42 RR1", "10M", "8M", "9M(9/39-s6", "5/40 RF 1.5", "4/41 RF 1.4", "8M", "6M", "^10/39 RF" ],
"510": [ "^1.3)", "^5/40 RF", "8M" ],
"519": [ "8/43; a", "^38(t)E is 1.3" ],
"520": [ "1.4 for 45", "44, and 1.4 for 45" ]
},
"german": {
@ -63,16 +67,22 @@
"new_caption": "2cm & 3.7cm FlaK LKW"
},
"96": {
"old_caption": "Opel 6700 &Buessing-NAG",
"old_caption": "Opel 6700 &",
"new_caption": "Opel 6700 & Buessing-NAG 4500"
},
"add": [
{ "_comment_": "This gets parsed as '4' and '5.1 GSW 39H(f) PaK' :-/",
"vo_note_id": "45.1", "caption": "GSW 39H(f) PaK", "page_no": 337, "page_pos": [380,561]
"vo_note_id": "45.1", "caption": "GSW 39H(f) PaK", "page_no": 342, "page_pos": [380,561]
},
{ "vo_note_id": "37.1", "caption": "Sturmtiger", "page_no": 532, "page_pos": [118,640] },
{ "vo_note_id": "88.1", "caption": "SdKfz 10/5", "page_no": 532, "page_pos": [399,713] }
{ "vo_note_id": "37.1", "caption": "Sturmtiger", "page_no": 538, "page_pos": [118,640] },
{ "vo_note_id": "88.1", "caption": "SdKfz 10/5", "page_no": 538, "page_pos": [399,713] }
]
},
"ordnance": {
"29": {
"old_caption": "3.7cm FlaK 43: O",
"new_caption": "3.7cm FlaK 43"
}
}
},
@ -99,7 +109,9 @@
"new_caption": "ISU-122 & ISU-152"
},
"add": [
{ "vo_note_id": "12.1", "caption": "T-28E M40(L)", "page_no": 364, "page_pos": [394,289] }
{ "vo_note_id": "47", "caption": "", "page_no": 359, "page_pos": [461,580] },
{ "vo_note_id": "12.1", "caption": "T-28E M40(L)", "page_no": 369, "page_pos": [394,289] },
{ "vo_note_id": "48", "caption": "Stuart III(a)", "page_no": 371, "page_pos": [394,515] }
]
}
},
@ -111,7 +123,7 @@
"new_caption": "M4A3E2 & M4A3E2 (L) Medium Tanks"
},
"17": {
"old_caption": "M4(105) & M4A3(105) MediumTanks",
"old_caption": "M4(105) & M4A3(105) Medium",
"new_caption": "M4(105) & M4A3(105) Medium Tanks"
}
}
@ -120,7 +132,7 @@
"british": {
"vehicles": {
"2": {
"old_caption": "(A17) Tetrarch & Tetrarch CS[Light Tanks Mk VII & Mk VII CS]",
"old_caption": "(A17) Tetrarch & Tetrarch CS",
"new_caption": "(A17) Tetrarch & Tetrarch CS [Light Tanks Mk VII & Mk VII CS]"
},
"6": {
@ -132,211 +144,34 @@
"new_caption": "(A12) Matilda II & II CS [Infantry Tank Mk II]"
},
"36": {
"old_caption": "Valentine & Churchill Bridgelay-ers",
"old_caption": "Valentine & Churchill Bridgelay-",
"new_caption": "Valentine & Churchill Bridgelayers"
},
"45": {
"old_caption": "Humber III & Otter Light Re-connaissance Cars",
"new_caption": "Humber III & Otter Light Reconnaissance Cars"
},
"82": {
"old_caption": "",
"new_caption": "30-cwt Lorry"
"54": {
"old_caption": "Staghound I(a) & II(a) Armoured",
"new_caption": "Staghound I(a) & II(a) Armoured Cars"
},
"83": {
"old_caption": "",
"new_caption": "3-Ton Lorry"
"add": [
{ "vo_note_id": "82", "caption": "30-cwt Lorry", "page_no": 422, "page_pos": [116,475] },
{ "vo_note_id": "83", "caption": "3-Ton Lorry", "page_no": 422, "page_pos": [116,287] }
]
},
"ordnance": {
"16": {
"old_caption": "OBL 4.5-in. Gun & 5.5-in. Gun-",
"new_caption": "OBL 4.5-in. Gun & 5.5-in. Gun-Howitzer"
}
}
},
"italian": {
"vehicles": {
"1": {
"old_caption": "LS/21 & LS/3",
"new_caption": "L5/21 & L5/30"
},
"2": {
"old_caption": "^L3/35: Derived from",
"new_caption": "L3/35"
},
"3": {
"old_caption": "^L3 aa: Some L3",
"new_caption": "L3 aa"
},
"4": {
"old_caption": "^L3 cc: During the early months",
"new_caption": "L3 cc"
},
"5": {
"old_caption": "^L3 Lf: Development of",
"new_caption": "L3 Lf"
},
"6": {
"old_caption": "^L6/40: Designed to replace",
"new_caption": "L6/40"
},
"7": {
"old_caption": "^Mll/39: This tank carried",
"new_caption": "M11/39"
},
"8": {
"old_caption": "^Ml3/40: Replacing the",
"new_caption": "M13/40"
},
"9": {
"old_caption": "^M14/41: This tank,",
"new_caption": "M14/41"
},
"10": {
"old_caption": "^M15/42: This, the last version",
"new_caption": "M15/42"
},
"11": {
"old_caption": "^MR/35(f): The Germans provided",
"new_caption": "MR/35(f)"
},
"12": {
"old_caption": "Semovente M40 & M41 da",
"new_caption": "Semovente M40 & M41 da 75/18"
},
"13": {
"old_caption": "^Semovente M42 da 75/1&75/32: The last model",
"new_caption": "Semovente M42 da 75/18 & 75/32"
},
"14": {
"old_caption": "^Semovente M43 da 105/25: Nicknathe",
"new_caption": "Semovente M43 da 105/25"
},
"15": {
"old_caption": "Semovente L40 da 47/32: The SMV",
"new_caption": "Semovente L40 da 47/32"
},
"16": {
"old_caption": "^Semovente M41M da 90/53: This AFV",
"new_caption": "Semovente M41M da 90/53"
},
"18": {
"old_caption": "^Lince: The Lince (Lynx)",
"new_caption": "Lince"
},
"19": {
"old_caption": "^Lancia lZM: In late 1912",
"new_caption": "Lancia 1ZM"
},
"20": {
"old_caption": "^Fiat 611A & 611BThese armoredcars",
"new_caption": "Fiat 611A & 611B"
},
"21": {
"old_caption": "^AB 40 & AB41These two auto",
"new_caption": "AB 40 & AB 41"
},
"22": {
"old_caption": "^Autoprotetto S37: This APC",
"new_caption": "Autoprotetto S37"
},
"23": {
"old_caption": "Autocannoni da",
"new_caption": "Autocannoni da 20/65(b) & 65/17(b)"
},
"24": {
"old_caption": "Autocannoni da",
"new_caption": "Autocannoni da 75/27 CK & 90/53"
},
"25": {
"old_caption": "^TL 37, TM 40 &TP 32",
"new_caption": "TL 37, TM 40 & TP 32"
},
"26": {
"old_caption": "^Autocarretta: As the portee",
"new_caption": "Autocarretta"
},
"27": {
"old_caption": "^Fiat 508 MC: Derived from",
"new_caption": "Fiat 508 MC"
},
"28": {
"old_caption": "^Autocarri L, M & P: The ItalianArmy",
"new_caption": "Autocarri L, M & P"
}
},
"ordnance": {
"1": {
"old_caption": "^Mortaio da 45 \"Brixia\": This weapon,",
"new_caption": "Mortaio da 45 \"Brixia\""
},
"2": {
"old_caption": "^Mortaio da 81/14: First usedi",
"new_caption": "Mortaio da 81/14"
},
"3": {
"old_caption": "^Fucile-cc S: Like several other",
"new_caption": "Fucile-cc S"
},
"4": {
"old_caption": "^Cannone-cc da 37/45: This was",
"new_caption": "Cannone-cc da 37/45"
},
"5": {
"old_caption": "^Cannone da 47/32: This was",
"new_caption": "Cannone da 47/32"
},
"6": {
"old_caption": "^Cannone da 65/17: This was",
"new_caption": "Cannone da 65/17"
},
"7": {
"old_caption": "^Cannone da 70/15: This",
"new_caption": "Cannone da 70/15"
},
"8": {
"old_caption": "^Obice da 75/13: The Skoda",
"new_caption": "Obice da 75/13"
},
"9": {
"old_caption": "^Cannone da 75/27: This was",
"new_caption": "Cannone da 75/27"
},
"10": {
"old_caption": "^Obice da 75/18: This game piece",
"new_caption": "Obice da 75/18"
},
"11": {
"old_caption": "^Cannone da 75/32: The 75/32",
"new_caption": "Cannone da 75/32"
},
"12": {
"old_caption": "^Obice da 100/17: Another old",
"new_caption": "Obice da 100/17"
},
"13": {
"old_caption": "^Cannone da 105/28: This was",
"new_caption": "Cannone da 105/28"
},
"14": {
"old_caption": "^Obice da 149/13: This piece",
"new_caption": "Obice da 149/13"
},
"15": {
"old_caption": "^Cannone da 149/35: Another",
"new_caption": "Cannone da 149/35"
},
"16": {
"old_caption": "^Cannone da 149/40: To replace",
"new_caption": "Cannone da 149/40"
},
"17": {
"old_caption": "^Cannone-mitragliera da 20/65: Thiswas",
"new_caption": "Cannone-mitragliera da 20/65"
},
"18": {
"old_caption": "^Cannone-aa da 75/39: This was",
"new_caption": "Cannone-aa da 75/39"
},
"add": [
{ "vo_note_id": "19", "caption": "Cannone-aa da 75/46", "page_no": 439, "page_pos": [283,42] },
{ "vo_note_id": "20", "caption": "Cannone-aa da 90/53", "page_no": 439, "page_pos": [384,541] }
{ "vo_note_id": "12", "caption": "Semovente M40 & M41 da 75/18", "page_no": 437, "page_pos": [442,715] }
]
}
},
@ -383,17 +218,16 @@
"new_caption": "Year-3 Type 14cm Naval Seacoast Gun"
},
"20": {
"old_caption": "Type 93 Twin-Mount High-Angle Ma-chine Gun",
"old_caption": "Type 93 Twin-Mount High-Angle Ma-",
"new_caption": "Type 93 Twin-Mount High-Angle Machine Gun"
},
"22": {
"old_caption": "Type 96 Single-, Twin-, & Triple-Mount Naval High-Angle Machine Can-",
"new_caption": "Type 96 Single-, Twin-, & Triple-Mount Naval High-Angle Machine Cannons"
},
"24": {
"old_caption": "Year-10 Type 12cm Naval High-AngleGun",
"new_caption": "Year-10 Type 12cm Naval High-Angle Gun"
}
},
"add": [
{ "vo_note_id": "22", "caption": "Type 96 Single-, Twin-, & Triple-Mount Naval High-Angle Machine Cannons", "page_no": 458, "page_pos": [395,713] }
]
}
},
@ -433,31 +267,51 @@
"french": {
"vehicles": {
"20": {
"old_caption": "Autocanon de 75 mle 97 & Autocanonde 75 Conus(b)",
"new_caption": "Autocanon de 75 mle 97 & Autocanon de 75 Conus(b)"
"old_caption": "CA, & Autocanon de 25 CA",
"new_caption": "Autocanon de 75 mle 97 & Autocanon de 75 Conus(b)",
"new_page_pos": [395,715]
},
"21": {
"old_caption": "Camion de Mitrailleuse Contre-Avions, Camion de 13.2 CAJ, Camion de",
"new_caption": "Camion de Mitrailleuse Contre-Avions, Camion de 13.2 CAJ, Camion de 20 CA, & Autocanon de 25 CA"
},
"36": {
"old_caption": "Peugeot 202, Citroën 23, & RenaultAGR2",
"old_caption": "Peugeot 202, Citroën 23, & Renault#NaAGR2",
"new_caption": "Peugeot 202, Citroën 23, & Renault AGR2"
},
"38": {
"old_caption": "Cr",
"new_caption": "Crusader II & III Tanks"
},
"39": {
"old_caption": "M",
"new_caption": "M4, M4A1, & M4A2 Medium Tanks"
},
"40": {
"old_caption": "M4A3(75)W, M4A3(76)W, & M4A3(105) Medium Tanks, & M4Tankdozer",
"new_caption": "M4A3(75)W, M4A3(76)W, & M4A3(105) Medium Tanks, & M4 Tankdozer"
}
},
"add": [
{ "vo_note_id": "11", "caption": "D2 & D2(L)", "page_no": 479, "page_pos": [395,715] },
{ "vo_note_id": "15", "caption": "AM Dodge(a)", "page_no": 480, "page_pos": [444,715] }
]
},
"ordnance": {
"6": {
"old_caption": "Canon Antichar de 47SA mle 37 APX",
"old_caption": "Canon Antichar de 47",
"new_caption": "Canon Antichar de 47 SA mle 37 APX"
},
"18": {
"old_caption": "Mitrailleuse de 13.2 CAJmle 30",
"new_caption": "Mitrailleuse de 13.2 CAJ mle 30"
}
},
"27": {
"old_caption": "M2A1 & M3 105mm Howitzers: C",
"new_caption": "M2A1 & M3 105mm Howitzers"
},
"add": [
{ "vo_note_id": "26", "caption": "OQF 25-Pounder Gun-Howitzer", "page_no": 493, "page_pos": [18,715] }
]
}
},
@ -494,7 +348,7 @@
"new_caption": "M3A3(a) FlaK 38"
},
"29": {
"old_caption": "Marmon-Herrington III(b) Armored",
"old_caption": "Marmon-Herrington III(b) Armored*2 TK DR",
"new_caption": "Marmon-Herrington III(b) Armored Cars"
},
"31": {
@ -502,7 +356,7 @@
"new_caption": "L5/30(i) & L3/35(i) & L6/40(i) & M13/40(i)"
},
"37": {
"old_caption": "Light Truck & Medium Truck &",
"old_caption": "Light Truck & Medium Truck &Heavy Truck",
"new_caption": "Light Truck & Medium Truck & Heavy Truck"
}
},
@ -513,7 +367,7 @@
"new_caption": "75M 19S"
},
"add": [
{ "vo_note_id": "20", "caption": "3.7cm Infantry Gun", "page_no": 502, "page_pos": [393,616] }
{ "vo_note_id": "20", "caption": "3.7cm Infantry Gun", "page_no": 508, "page_pos": [393,616] }
]
}
},
@ -555,10 +409,17 @@
"new_vo_note_id": "16",
"new_caption": "40M Nimrod"
},
"39": {
"old_caption": "PzKpfw IVD(g), PzKpfw IVF1(g),",
"new_caption": "PzKpfw IVD(g), PzKpfw IVF1(g), & PzKpfw IVH(g)"
},
"50": {
"old_caption": "Light Truck, Medium Truck, &Heavy Truck",
"new_caption": "Light Truck, Medium Truck, & Heavy Truck"
}
},
"add": [
{ "vo_note_id": "48", "caption": "RSO(g)", "page_no": 521, "page_pos": [394,713] }
]
},
"ordnance": {
"20": {
@ -689,7 +550,10 @@
"old_caption": "ItK/31(r)",
"new_vo_note_id": "39",
"new_caption": "76 ItK/31(r)"
}
},
"add": [
{ "vo_note_id": "24", "caption": "105 H/37", "page_no": 553, "page_pos": [394,713] }
]
}
},
@ -708,7 +572,7 @@
"new_caption": "M4A3E8(a) Medium Tank & M4A3E8 Dozer(a)"
},
"47": {
"old_caption": "Oxford Carrier, MMG & Oxford Car-rier, HMG",
"old_caption": "Oxford Carrier, MMG & Oxford Car-",
"new_caption": "Oxford Carrier, MMG & Oxford Carrier, HMG"
},
"57": {

@ -15,7 +15,7 @@ from asl_rulebook2.utils import parse_page_numbers, fixup_text, extract_parens_c
# ---------------------------------------------------------------------
_DEFAULT_ARGS = {
"pages": "10-41",
"pages": "10-42",
"index_vp_left": 0, "index_vp_right": 565, "index_vp_top": 715, "index_vp_bottom": 20, # viewport
"first_title": "a", "last_title": "X#", # first/last index entries
}

@ -111,7 +111,7 @@ class PageIterator:
class PageElemIterator:
"""Iterate over each element in a page."""
def __init__( self, lt_page, elem_filter=None, sort_elems=False ):
def __init__( self, lt_page, elem_filter=None, sort_elems=False, centre_adjust=0 ):
self.lt_page = lt_page
# collect all the elements (so that they can be sorted)
self._elems = []
@ -127,7 +127,7 @@ class PageElemIterator:
walk( lt_page, 0 )
if sort_elems:
def sort_key( elem ):
col_no = 0 if elem[1].x0 < lt_page.width/2 else 1
col_no = 0 if elem[1].x0 < lt_page.width/2 + centre_adjust else 1
# NOTE: Some elements that should be aligned are actually misaligned by a miniscule amount (e.g. 10^-5),
# so to stop this from resulting in the wrong sort order, we truncate the decimal places.
# NOTE: Characters are often rendered in different fonts, with bounding boxes that don't align neatly.

@ -140,7 +140,7 @@ gPrepareApp.component( "upload-panel", {
Click on the button, and select your copy of MMP's eASLRB.
<div class="info"> You <u>must</u> use the <a href="https://www.wargamevault.com/product/344879/Electronic-Advanced-Squad-Leader-Rulebook" target="_blank">offical MMP eASLRB</a>. <br>
A scan of a printed rulebook <u>will not work</u>!
<p> You should use v1.07 of the eASLRB PDF (normal version, not the "inherited zoom" version). Other versions <i>may</i> work, but may have warnings and/or errors. </p>
<p> You should use v2.01 of the eASLRB PDF (normal version, not the "inherited zoom" version). Other versions <i>may</i> work, but may have warnings and/or errors. </p>
</div>
</div>
</div>

Loading…
Cancel
Save