Index: src/test/display/MdrDisplay.java
===================================================================
--- src/test/display/MdrDisplay.java	(revision 574)
+++ src/test/display/MdrDisplay.java	(working copy)
@@ -12,17 +12,22 @@
  */
 package test.display;
 
-import java.io.UnsupportedEncodingException;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
 import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.BitSet;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 
+import uk.me.parabola.imgfmt.app.ImgFileReader;
 import uk.me.parabola.imgfmt.app.srt.Sort;
 
 /**
@@ -46,9 +51,23 @@
 	private int citySizeFlagged;
 	private Charset charSet;
 	private Map<Integer, String> stringTable;
+	private BitSet stringOffsets = new BitSet();
+	
 	private int start;
 	private int record;
 
+	private HuffNode root;
+	/** lookup table with minimal code for each depth */
+	private int[] huffmanLUT1;
+	private byte[] huffmanLUT2;
+	
+	private List<byte[]> huf2 = new ArrayList<>();
+	private List<Mdr16Tab> huf3 = new ArrayList<>();
+	private ByteBuffer huffmanBuf = ByteBuffer.allocate(250);
+
+	private long mdr15UncompressedSize;
+
+	@Override
 	protected void print() {
 		readCommonHeader();
 
@@ -63,6 +82,9 @@
 		 * none is found then a generic method is used to print the
 		 * section
 		 */
+		if (compressedStrings) 
+			printSect16(); // fill Huffman tree
+		
 		for (int i = 1; i < numberOfSections(); i++) {
 			if (!want(i))
 				continue;
@@ -75,12 +97,49 @@
 			} catch (InvocationTargetException e) {
 				Throwable targetException = e.getTargetException();
 				System.out.printf("Failed in section %d: %s\n", i, targetException);
+				
 			} catch (IllegalAccessException e) {
 				e.printStackTrace();
 			}
 		}
+		
+		if (root != null) {
+			// if all sections were processed, print Huffman frequencies
+			boolean allCounted = true;
+			for (int i = 0; i < wanted.length; i++) {
+				if (!wanted[i])
+					allCounted = false;
+			}
+			if (allCounted|| true) {
+				outStream.printf("compressed/uncomressed mdr 15 size: %d / %d %n", getSection(15).getLen(), mdr15UncompressedSize);
+				printHuffmanTree(0, root);
+			}
+		}
 	}
 
+	private void printHuffmanTree(int depth, HuffNode node) {
+		if (node == null)
+			return;
+		if (node.ch != null) {
+			outStream.printf("depth:%d %s freq: %d %n", depth, displayChar((byte) (node.ch & 0xff)), node.freq);
+		}
+		printHuffmanTree(depth + 1, node.left);
+		printHuffmanTree(depth + 1, node.right);
+	}
+
+	/** return a byte interpreted as char and with hex code.
+	 * 
+	 * @param val the byte 
+	 * @return
+	 */
+	private String displayChar(byte val) {
+		byte[] bb = {val};
+		CharBuffer cbuf = charSet.decode(ByteBuffer.wrap(bb)); 
+		char v = cbuf.get();
+		
+		return String.format("'%c' 0x%02x", v == 0 ? '.' : v, val & 0xff);
+	}
+
 	/**
 	 * Prints an unknown section.  If there is a record size then each
 	 * record is printed separately.
@@ -115,6 +174,7 @@
 		int sublen;
 	}
 
+	@SuppressWarnings("unused")
 	private void printSect1() {
 		Displayer d = new Displayer(reader);
 		d.setTitle("MDR 1 (maps)");
@@ -167,6 +227,7 @@
 	/**
 	 * A list of POI types without subtypes
 	 */
+	@SuppressWarnings("unused")
 	private void printSect3() {
 		Displayer d = new Displayer(reader);
 		d.setTitle("MDR 3 (polygon types)");
@@ -191,6 +252,7 @@
 	/**
 	 * A list of POI types.
 	 */
+	@SuppressWarnings("unused")
 	private void printSect4() {
 		Displayer d = new Displayer(reader);
 		d.setTitle("MDR 4 (POI types)");
@@ -303,6 +365,7 @@
 	/**
 	 * A list of zips.  It is alphabetically arranged.
 	 */
+	@SuppressWarnings("unused")
 	private void printSect6() {
 		Displayer d = new Displayer(reader);
 		d.setTitle("MDR 6 (ZIPs)");
@@ -332,6 +395,7 @@
 	/**
 	 * Contains an ordered list of pointers to street names.
 	 */
+	@SuppressWarnings("unused")
 	private void printSect7() {
 		Displayer d = new Displayer(reader);
 		d.setTitle("MDR 7 (street names)");
@@ -383,6 +447,7 @@
 		d.print(outStream);
 	}
 
+	@SuppressWarnings("unused")
 	private void printSect8() {
 		Displayer d = new Displayer(reader);
 		d.setTitle("MDR 8 (index into streets)");
@@ -392,6 +457,7 @@
 	/**
 	 * POI / Polygon groups.
 	 */
+	@SuppressWarnings("unused")
 	private void printSect9() {
 		Displayer d = new Displayer(reader);
 		d.setTitle("MDR 9 (Index into mdr10, grouped based on type)");
@@ -416,6 +482,7 @@
 	/**
 	 * POI types.  Also has a pointer into MDR 11.  Seems like a waste.
 	 */
+	@SuppressWarnings("unused")
 	private void printSect10() {
 		// Looks variable length
 		Displayer d = new Displayer(reader);
@@ -507,6 +574,7 @@
 	 * and the offset in LBL.  If it has an associated city (or is a city itself), there is
 	 * a reference back into MDR 5.
 	 */
+	@SuppressWarnings("unused")
 	private void printSect11() {
 		Displayer d = new Displayer(reader);
 		d.setTitle("MDR 11 (POIs)");
@@ -546,6 +614,7 @@
 		d.print(outStream);
 	}
 
+	@SuppressWarnings("unused")
 	private void printSect12() {
 		Displayer d = new Displayer(reader);
 		d.setTitle("MDR 12 (index into POI)");
@@ -579,6 +648,7 @@
 	 * This is an ordered list containing pointers into the text strings,
 	 * probably for regions, arranged per map.
 	 */
+	@SuppressWarnings("unused")
 	private void printSect13() {
 		Displayer d = new Displayer(reader);
 		d.setTitle("MDR 13 (regions)");
@@ -608,6 +678,7 @@
 	/**
 	 * Countries for each map.
 	 */
+	@SuppressWarnings("unused")
 	private void printSect14() {
 		Displayer d = new Displayer(reader);
 		d.setTitle("MDR 14 (country)");
@@ -635,6 +706,7 @@
 	 * There are two formats possible, this is the simple straightforward
 	 * one.  There is an alternate one that is compressed in some way.
 	 */
+	@SuppressWarnings("unused")
 	private void printSect15() {
 		Displayer d = new Displayer(reader);
 		d.setTitle("MDR 15 (strings)");
@@ -646,7 +718,7 @@
 		reader.position(start);
 
 		if (compressedStrings) {
-			// don't currently know this
+			// strings are compressed with Huffman encoding
 			d.rawValue((int) (end - start));
 		} else {
 			// If we have already read the strings then don't bother to do it all again.
@@ -669,11 +741,12 @@
 	 * This is some kind of codebook which is used to decode the strings
 	 * in MDR 15 if they are compressed.
 	 */
+	@SuppressWarnings("unused")
 	private void printSect16() {
 		if (!compressedStrings) 
 			return;
 		Displayer d = new Displayer(reader);
-		d.setTitle("MDR 16 (decompression codebook?)");
+		d.setTitle("MDR 16 (decompression codebook Huffman tree)");
 
 		Section s = getSection(16);
 		long start = s.getStart();
@@ -680,12 +753,197 @@
 		long end = s.getEnd();
 		d.setSectStart(start);
 		reader.position(start);
+		DisplayItem item = d.byteItem();
+		item.addText("???");
+		item = d.byteItem();
+		item.addText("???");
+		item = d.byteItem();
+		item.addText("???");
+		item = d.byteItem();
+		int lastLevel = item.getValue();
+		item.addText("1st level with struct: %d", item.getValue());
+		item = d.byteItem();
+		int rowsTab1 = item.getValue();
+		item.addText("rows in 1st table: %d", item.getValue());
+		item = d.byteItem();
+		int symWidth = item.getValue();
+		item.addText("symbol width? %d", symWidth);
+		item = d.byteItem();
+		item.addText("number of bytes in last block * 2 + 1 %d->%d bytes", item.getValue(), item.getValue()>>1);
+		int level = lastLevel;
+		BitSet bs = new BitSet();
+		int count = 0;
+		int structBytes = 1;
+		huffmanLUT1= new int[lastLevel+1];
+		while (structBytes * symWidth < lastLevel) {
+			structBytes++;
+		}
+		int lastCount = 0;
+		List<Mdr16Tab> levels = new ArrayList<>();
+		for (int i = 0; i <= lastLevel; i++) {
+			levels.add(new Mdr16Tab());
+		}
+		for (int i = 0; i< rowsTab1; i++) {
+			item = d.intItem(structBytes);
+			int minCode = item.getValue();
+			item.addText("struct/mask for depth %d ???", level);
+			item = d.byteItem();
+			level = item.getValue();
+			huffmanLUT1[level] = minCode >> (lastLevel - level);
+			item.addText("level: %d, minCode=", item.getValue(), huffmanLUT1[level]);
+			item = d.byteItem();
+			count = item.getValue();
+			int delta = count - lastCount;
+			long off1 = end - count + 1;
+			item.addText("count byte %d, delta = %d", item.getValue(), delta);
+			Mdr16Tab levelDesc = levels.get(level);
+			levelDesc.depth = level;
+			levelDesc.offset = count;
+			huf3.add(levelDesc);
+			--level;
+			
+			lastCount = count;
+		}
+		item.addText("\n---- lookup table ----" );
+		long tab2Pos = reader.position();
+		huffmanLUT2 = reader.get(64);
+		reader.position(tab2Pos);
+		List<LinkedList<Byte>> levelVals = new ArrayList<>();
+		for (int i = 0; i <= lastLevel; i++) {
+			levelVals.add(new LinkedList<>());
+			huf2.add(new byte[0]);
+		}
+		
+		for (int i = 0; i < 32; i++) {
+			item = d.charItem();
+			int val = item.getValue() >>> 8;
+			
+			int stat = (byte) item.getValue();
+			if (stat % 2 == 1) {
+				int lvl = stat >> 1;
+				LinkedList<Byte> lst = levelVals.get(lvl);
+				if (lst.isEmpty() || lst.getLast() != val)
+					lst.add((byte) val);
 
+			}
+			String prefix = Integer.toBinaryString(i);
+			// add leading 0 to make it 5 bytes long
+			prefix = "00000".substring(0, 5 - prefix.length()) + prefix;
+			if (stat % 2 == 1)
+				item.addText("prefix %s : stat=%d v=%s", prefix, stat, displayChar((byte) val));
+			else
+				item.addText("prefix %s : stat=%d v=%d ???", prefix, stat, val);
+		}
+		
+		long posRest = reader.position();
+		
+		d.rawValue((int) (end - posRest));
+		reader.position(posRest);
+		long lastOff = 0;
+		for (int i = 0; i < levels.size(); i++) {
+			Mdr16Tab info = levels.get(i);
+			int len;
+			if (info.depth == 0)
+				continue;
+			if (lastOff == 0)
+				len = (int) (end - reader.position()) - info.offset;
+			else
+				len = (int) (lastOff - info.offset);
+			lastOff = info.offset;
+			reader.position(posRest + info.offset);
+			if (len < 0) {
+				outStream.print("failed to read Huffman tree in MDR section 16");
+				break;
+			}
+			
+			LinkedList<Byte> lst = levelVals.get(info.depth);
+			for (int k = 0; k < len; k++)
+				lst.add(reader.get());
+			reader.position(reader.position() - len);
+			byte[] bytes = reader.get(len);
+			huf2.set(info.depth, bytes );
+			
+		}
+		if (root != null) {
+			reader.position(posRest);
+			lastOff = 0;
+			for (int i = 0; i < levels.size(); i++) {
+				Mdr16Tab info = levels.get(i);
+				int len;
+				if (info.depth == 0)
+					continue;
+				if (lastOff == 0)
+					len = (int) (end - reader.position()) - info.offset;
+				else
+					len = (int) (lastOff - info.offset);
+				lastOff = info.offset;
+				reader.position(posRest + info.offset);
+				if (len < 0) {
+					outStream.print("failed to read Huffman tree in MDR section 16");
+					break;
+				}
+				byte[] bb = reader.get(len);
+				String decoded = charSet.decode(ByteBuffer.wrap(bb)).toString();
+				outStream.printf("depth %d : >%s<%n", info.depth, decoded);
+			}
+			outStream.flush();
+		}
 		// don't currently know this
-		d.rawValue((int) (end - start));
-		d.print(outStream);
+		reader.position(start);
+		if (root == null)	
+			buildHuffmanTree(levelVals);
+		else {
+			d.print(outStream);
+			printSectUnknown(16);
+		}
+		return;
 	}
 
+	/** reconstruct Huffman tree, probably not done by Garmin software */
+	private void buildHuffmanTree(List<LinkedList<Byte>> levelVals) {
+		root = null;
+		int code = -1;
+		boolean firstCode = true;
+		int off = 0;
+		byte[] bb = {0};
+		outStream.print("parsing Huffman tree in MDR 16\n");
+		for (int i = 0; i < levelVals.size(); i++) {
+			LinkedList<Byte> vals = levelVals.get(i);
+			if (firstCode)
+				code = 1 << i;
+			else 
+				code <<= 1;
+			if (!vals.isEmpty()) {
+				firstCode = false;
+				while (!vals.isEmpty()) {
+					code--;
+					String x = Integer.toBinaryString(code);
+					if (x.length() < i) {
+						// add leading zeros to code
+						x = "0000000000000000000000000000000000000".substring(0, i - x.length()) + x;
+					}
+					bb[0] = vals.removeLast();
+					outStream.printf("off:%d \"%s\" %s %n", off, x, displayChar(bb[0]));
+					off++;
+					CharBuffer cbuf = charSet.decode(ByteBuffer.wrap(bb)); 
+					char v = cbuf.get();
+					addHuffmanNode(x, v);
+				}
+			}
+		}
+		outStream.flush();
+	}
+
+	private static class Mdr16Tab {
+		int depth;
+		int offset;
+		
+		@Override
+		public String toString() {
+			// TODO Auto-generated method stub
+			return "depth=" + depth + ", offset=" + offset;
+		}
+	}
 	/**
 	 * MDR 17: String table
 	 *
@@ -692,6 +950,7 @@
 	 * My personal guess is, that this is a search table for
 	 * sub strings starting with the given short string
 	 */
+	@SuppressWarnings("unused")
 	private void printSect17() {
 		Displayer d = new Displayer(reader);
 		d.setTitle("MDR 17 (indexes based on initial sub strings)");
@@ -760,6 +1019,7 @@
 	/**
 	 * MDR 18: Looks like a mapping to MDR 11 or MDR 19
 	 */
+	@SuppressWarnings("unused")
 	private void printSect18() {
 		Displayer d = new Displayer(reader);
 		d.setTitle("MDR 18 (poi type index into 19)");
@@ -782,6 +1042,7 @@
 		d.print(outStream);
 	}
 
+	@SuppressWarnings("unused")
 	private void printSect19() {
 		Displayer d = new Displayer(reader);
 		d.setTitle("MDR 19 (poi sorted by type)");
@@ -888,19 +1149,23 @@
 		d.print(outStream);
 
 	}
-
+	
+	@SuppressWarnings("unused")
 	private void printSect20() {
 		printSect2x("MDR 20 (streets by city)", 20);
 	}
 
+	@SuppressWarnings("unused")
 	private void printSect21() {
 		printSect2x("MDR 21 (streets by region)", 21);
 	}
 
+	@SuppressWarnings("unused")
 	private void printSect22() {
 		printSect2x("MDR 22 (streets by country)", 22);
 	}
 
+	@SuppressWarnings("unused")
 	private void printSect23() {
 		Displayer d = new Displayer(reader);
 		d.setTitle("MDR 23 (sorted regions)");
@@ -930,6 +1195,7 @@
 		d.print(outStream);
 	}
 
+	@SuppressWarnings("unused")
 	private void printSect24() {
 		Displayer d = new Displayer(reader);
 		d.setTitle("MDR 24 (sorted countries)");
@@ -958,6 +1224,7 @@
 		d.print(outStream);
 	}
 
+	@SuppressWarnings("unused")
 	private void printSect25() {
 		Displayer d = new Displayer(reader);
 		d.setTitle("MDR 25 (cities by country)");
@@ -986,6 +1253,7 @@
 		d.print(outStream);
 	}
 
+	@SuppressWarnings("unused")
 	private void printSect26() {
 		Displayer d = new Displayer(reader);
 		d.setTitle("MDR 26 (mdr28 ordered by country)");
@@ -1018,6 +1286,7 @@
 		d.print(outStream);
 	}
 
+	@SuppressWarnings("unused")
 	private void printSect27() {
 		Displayer d = new Displayer(reader);
 		d.setTitle("MDR 27 (cities by region?)");
@@ -1046,6 +1315,7 @@
 		d.print(outStream);
 	}
 
+	@SuppressWarnings("unused")
 	private void printSect28() {
 		Displayer d = new Displayer(reader);
 		d.setTitle("MDR 28 (region names with pointers)");
@@ -1095,6 +1365,7 @@
 		d.print(outStream);
 	}
 
+	@SuppressWarnings("unused")
 	private void printSect29() {
 		Displayer d = new Displayer(reader);
 		d.setTitle("MDR 29 (country names with pointers)");
@@ -1153,7 +1424,19 @@
 			d.item().addText("max %d", m);
 		d.print(outStream);
 	}
+	
+	/** used with 30/31 and 32/33 */
+	private void displayString(int sectNum, int lastOff, int off, Displayer d) {
+		long saveOff = reader.position();
 
+		reader.position(getSection(sectNum).getStart()+ lastOff);
+		byte[] bytes = reader.get(off - lastOff);
+		DisplayItem item = d.item();
+		item.addText("mdr%d string: %s", sectNum, new String(bytes, StandardCharsets.ISO_8859_1));
+		reader.position(saveOff);
+	}
+
+	@SuppressWarnings("unused")
 	private void printSect30() {
 		Displayer d = new Displayer(reader);
 		d.setTitle("MDR 30 (pointers to mdr31)");
@@ -1168,33 +1451,30 @@
 
 		reader.position(s.getStart());
 
-		long start;
 		int lastOff = 0;
+		int maxOff = s31.getLen();
+		int mask = 0xff;
+		while (mask < maxOff) {
+			mask <<= 8;
+			mask += 0xff;
+		}
 		while (reader.position() < s.getEnd()) {
-			System.out.println("GOT " + reader.position());
-
 			int off = d.intValue(recsize, "Offset in mdr31: %d") & 0xffff;
+			if (off > maxOff)
+				off = off & mask;
 			if (off > 0) {
-
-				long saveOff = reader.position();
-
-				reader.position(s31.getStart() + lastOff);
-				byte[] bytes = reader.get(off - lastOff);
-				DisplayItem item = d.item();
-				try {
-					item.addText("mdr31 string: %s", new String(bytes, "latin1"));
-				} catch (UnsupportedEncodingException e) {
-					item.addText("Cannot decode string");
-				}
-
-				reader.position(saveOff);
+				displayString(31, lastOff, off, d);
 				lastOff = off;
 			}
-
 		}
+		// remaining data
+		if (lastOff < maxOff) {
+			displayString(31, lastOff, maxOff, d);
+		}
 		d.print(outStream);
 	}
 
+	@SuppressWarnings("unused")
 	private void printSect32() {
 		Displayer d = new Displayer(reader);
 		d.setTitle("MDR 32 (pointers to mdr33)");
@@ -1208,29 +1488,27 @@
 
 		reader.position(s.getStart());
 
-		long start;
 		int lastOff = 0;
+		int maxOff = s33.getLen();
+		int mask = 0xff;
+		while (mask < maxOff) {
+			mask <<= 8;
+			mask += 0xff;
+		}
 		while (reader.position() < s.getEnd()) {
 
 			int off = d.intValue(recsize, "Offset in mdr33: %d");
+			if (off > maxOff)
+				off = off & mask;
 			if (off > 0) {
-
-				long saveOff = reader.position();
-
-				reader.position(s33.getStart() + lastOff);
-				byte[] bytes = reader.get(off - lastOff);
-				DisplayItem item = d.item();
-				try {
-					item.addText("mdr33 string: %s", new String(bytes, "latin1"));
-				} catch (UnsupportedEncodingException e) {
-					item.addText("Cannot decode string");
-				}
-
-				reader.position(saveOff);
+				displayString(33, lastOff, off, d);
 				lastOff = off;
 			}
-
 		}
+		// remaining data
+		if (lastOff < maxOff) {
+			displayString(33, lastOff, maxOff, d);
+		}
 		d.print(outStream);
 	}
 
@@ -1266,16 +1544,289 @@
 	 * @return The resultant string.
 	 */
 	private String getTextString(int off) {
+		if (off < 0 || off > getSection(15).getLen())
+			return "invalid offset:" + off;
 		if (compressedStrings) {
-			return "(offset: " + off + ")";
+			if (stringTable == null)
+				stringTable = new LinkedHashMap<>();
+//			if (root == null) {
+//				// depth 3, prefix 0x07: 111
+//				addHuffmanNode("111", 'A'); 
+//				
+//				// depth 4, prefix 0x09: 1001
+//				addHuffmanNode("0111", '\0'); // 0x00
+//				addHuffmanNode("1000", ' '); //0x20
+//				addHuffmanNode("1001", 'E'); 
+//				addHuffmanNode("1010", 'N');
+//				addHuffmanNode("1011", 'O');
+//				addHuffmanNode("1100", 'I');
+//				addHuffmanNode("1101", 'R'); 
+//				
+//				// depth 5, prefix 0x0b: 1101
+//				addHuffmanNode("00101", 'C');
+//				addHuffmanNode("00110", 'S');
+//				addHuffmanNode("00111", 'D');
+//				addHuffmanNode("01000", 'T');
+//				addHuffmanNode("01001", 'M');
+//				addHuffmanNode("01010", 'V');
+//				addHuffmanNode("01011", 'J');
+//				addHuffmanNode("01100", 'K');
+//				addHuffmanNode("01101", 'L');
+//				// depth 6
+//				addHuffmanNode("000101", 'G');
+//				addHuffmanNode("000110", 'Z');
+//				addHuffmanNode("000111", 'U');
+//				addHuffmanNode("001000", 'P');
+//				addHuffmanNode("001001", 'B');
+//				// depth 7
+//				addHuffmanNode("0000110", 'H');
+//				addHuffmanNode("0000111", '1');
+//				addHuffmanNode("0001000", '2');
+//				addHuffmanNode("0001001", '.');
+//				// depth 8
+//				addHuffmanNode("00000011", '5');
+//				addHuffmanNode("00000100", '6');
+//				addHuffmanNode("00000101", '-');
+//				addHuffmanNode("00000110", '7');
+//				addHuffmanNode("00000111", '3');
+//				addHuffmanNode("00001000", '8');
+//				addHuffmanNode("00001001", '9');
+//				addHuffmanNode("00001010", '0');
+//				addHuffmanNode("00001011", '4');
+//				// depth 9
+//				addHuffmanNode("000000010", 'F');
+//				addHuffmanNode("000000011",',');
+//				addHuffmanNode("000000100", '(');
+//				addHuffmanNode("000000101", ')');
+//				// depth 10
+//				addHuffmanNode("0000000011", '*');
+//				// depth 11
+//				addHuffmanNode("00000000011", 'Y');
+//				addHuffmanNode("00000000100", '"');
+//				addHuffmanNode("00000000101", (char) 0x06); // non-print4
+//				// depth 12
+//				addHuffmanNode("000000000011", 'W');
+//				addHuffmanNode("000000000100", 'X');
+//				addHuffmanNode("000000000101", (char) 0x05); //non-print some shield code
+//				// depth 13
+//				addHuffmanNode("0000000000100", '&');
+//				addHuffmanNode("0000000000101", (char) 0x1f); //non-print2
+//				// depth 14
+//				addHuffmanNode("00000000000011", 'Q');
+//				addHuffmanNode("00000000000100", 'Š'); 
+//				addHuffmanNode("00000000000101", '\''); // mdr16 says 0x8a
+//				addHuffmanNode("00000000000110", 'Ë'); 
+//				addHuffmanNode("00000000000111", '/');
+//				// depth 15
+//				addHuffmanNode("000000000000100", 'Æ');
+//				addHuffmanNode("000000000000101", 'È');
+//				// depth 16
+//				addHuffmanNode("0000000000000101", (char) 0x04); // non-print3
+//				addHuffmanNode("0000000000000110", '!'); 
+//				addHuffmanNode("0000000000000111", (char) 0x9e); //Ž
+//				// depth 17
+//				addHuffmanNode("00000000000000110",'+');
+//				addHuffmanNode("00000000000000111", '–'); // different to minus mdr16: 0x96
+//				addHuffmanNode("00000000000001000", 'Á'); //Á
+//				addHuffmanNode("00000000000001001", 'Ü'); 
+//				// depth 18
+//				addHuffmanNode("000000000000000110", '_');  
+//				addHuffmanNode("000000000000000111", '{');
+//				addHuffmanNode("000000000000001000", '“'); // mdr16: 0x93
+//				addHuffmanNode("000000000000001001", 'É');
+//				addHuffmanNode("000000000000001010", 'Ð'); 
+//				addHuffmanNode("000000000000001011", 'Ö');
+//				// depth 19
+//				addHuffmanNode("0000000000000000011", '?'); 
+//				addHuffmanNode("0000000000000000100", '`'); // mdr16: 0x60
+//				addHuffmanNode("0000000000000000101", '„'); // mdr16: 0x84
+//				addHuffmanNode("0000000000000000110", 'ž'); // mdr16: 0x8e 
+//				addHuffmanNode("0000000000000000111", '’'); // mdr16: 0x92
+//				addHuffmanNode("0000000000000001000", 'Ä');
+//				addHuffmanNode("0000000000000001001", 'Ø');
+//				addHuffmanNode("0000000000000001010", 'Ú'); 
+//				addHuffmanNode("0000000000000001011", 'Û');
+//				// depth 20
+//				addHuffmanNode("00000000000000000000", '#'); // offset into MDR 30/31? MapSource displays "HWY " 
+//				addHuffmanNode("00000000000000000001", '%'); // 
+//				addHuffmanNode("00000000000000000010", ':');
+//				addHuffmanNode("00000000000000000011", ';');
+//				addHuffmanNode("00000000000000000100", (char) 0x8f);
+//				addHuffmanNode("00000000000000000101", (char) 0xb8);
+//				StringBuilder vals = new StringBuilder();
+//				StringBuilder struct = new StringBuilder();
+//				int minDepth = 3;
+//				int maxDepth = 21;
+//				for (int depth = maxDepth; depth >= minDepth; depth--) {
+//					struct.setLength(0);
+//					displayHuffman(0, depth, vals, struct, root);
+//					String chars = vals.toString();
+//					outStream.print("depth " + depth + " :>" + chars + "< num: " + vals.length() + "\n");
+//					outStream.flush();
+//					vals.setLength(0);
+//					
+//				}
+//				int count = 0;
+//				for (int i = 0; i< struct.length(); i++) {
+//					if (struct.charAt(i) == '0') {
+//						count++;
+//					}
+//				}
+//				BitSet bs = new BitSet();
+////				for (int i = 200; i > 0; i--) {
+////					if (i % 4 == 0 && i < struct.length())
+////						struct.insert(i, ' ');
+////				}
+//				String s = struct.toString();
+//				outStream.print("struct " + s+ "\n");
+//				outStream.flush();
+//				for (int i = 0; i < s.length();i++) {
+//					if (s.charAt(i) == '1')
+//						bs.set(i);
+//				}
+//				byte[] bytes = bs.toByteArray();
+//				long dd = 4;
+//			}
+			String s = stringTable.get(off);
+			if (s != null)
+				return s;
+			stringOffsets.set(off);
+			long mdr15Start = getSection(15).getStart();
+			long savePos = reader.position();
+			
+			reader.position(mdr15Start + off);
+			s = decode(reader); 
+			mdr15UncompressedSize += s.length();
+			reader.position(mdr15Start + off);
+			String sFast = decodeWithTable(); 
+			if (!s.equals(sFast)) {
+				System.err.println(String.format("Huffman decoders mismatch %s <> %s", s, sFast));
+				long dd = 4;
+			}
+			reader.position(savePos);
+			stringTable.put(off, s);
+			return s + "(offset: " + off + ")";
 		}
-
+		
 		if (stringTable == null)
 			readAllStrings();
 
 		return stringTable.get(off);
 	}
+	
+	private String decodeWithTable() {
+		int b = 0;
+		int remaining = 0;
+		boolean searching = false;
+		long code = 0;
+		int depth = 0;
+		int stat = 0;
+		int minDepth = 0;
+		huffmanBuf.clear();
+		 
+		while (true) {
+			if (remaining < 5) {
+				b <<= 8;
+				b &=  0xffff;
+				b |= reader.get() & 0xff;
+				remaining += 8;
+			}
+			if (searching) {
+				depth++;
+				byte[] vals = huf2.get(depth);
+				// read next bit
+				int nextBit = (b >>> (remaining -1)) & 1;
+				remaining--;
+				code <<= 1;
+				code |= nextBit;
+				if (depth >= minDepth && vals.length > 0) {
+					int idx = (int) (code - huffmanLUT1[depth]);
+					if (idx >= 0) {
+						if (idx >= vals.length) {
+							long dd = 4;
+						}
+						byte v = vals[idx];
+						huffmanBuf.put(v);	
+						searching = false;
+					}
+				}
+			} else {
+				int off = ((b >>> (remaining - 5)) & 0x1f) * 2;
+				stat = huffmanLUT2[off];
+				int v = huffmanLUT2[off+1];
+				if (stat % 2 == 0) {
+					searching = true;
+					remaining -= 5;
+					code = off/2;
+					depth = 5;
+					Mdr16Tab levelDesc = huf3.get(v);
+					minDepth = levelDesc.depth;
+					if (stat == 0) {
+						long dd = 4; 
+						
+					}
+					continue;
+				} 
+				// lookup table contains value
+				huffmanBuf.put((byte) v);
+				if (v == 0)
+					break;
+				int usedBits = stat/2;
+				remaining -= usedBits;
+			}			
+		}
+		return new String(huffmanBuf.array(), 0, huffmanBuf.position()-1, charSet);
+	}
 
+	/** pre-order-traversal */
+	private void displayHuffman(int depth, int wantedDepth, StringBuilder vals, StringBuilder struct, HuffNode node) {
+		if (node.ch != null && wantedDepth == depth) {
+			vals.append(node.ch);
+		}
+		struct.append(node.ch != null ? '1' : '0');
+		if (node.left != null)
+			displayHuffman(depth + 1, wantedDepth, vals, struct, node.left);
+		if (node.right != null)
+			displayHuffman(depth + 1, wantedDepth, vals, struct, node.right);
+	}
+
+	String decode (ImgFileReader reader) {
+		StringBuilder sb = new StringBuilder();
+		StringBuilder code = new StringBuilder();
+		int mask = 0;
+		byte b = 0;
+		HuffNode next = root;
+		long lastOff = getSection(15).getEnd();
+		while (true) {
+			if (reader.position() > lastOff) {
+				return sb.toString();
+			}
+			if (next == null) {
+				sb.append('?');
+				return sb.toString();
+			}
+			if (next.ch != null) {
+				next.freq++;
+				if (next.ch == 0)
+					return sb.toString();
+				
+				sb.append(next.ch);
+				next = root;
+				code.setLength(0);
+			}
+			if (mask == 0) {
+				mask = 0x80;
+				b = reader.get();
+			}
+			int bit = (b & mask) == 0 ? 0 : 1;
+			code.append(bit == 1 ? '1' : '0');
+			mask >>= 1;
+			if (next.ch == null) {
+				next = bit == 0 ? next.left : next.right; 
+			}
+		}
+	}
+
 	private void readAllStrings() {
 		if (compressedStrings)
 			return;  // we don't know how to do this yet.
@@ -1436,10 +1987,7 @@
 	 */
 	private boolean want(int n) {
 		Section section = getSection(n);
-		if (section != null && section.getLen() > 0 && wanted[n])
-			return true;
-		else
-			return false;
+		return section != null && section.getLen() > 0 && wanted[n];
 	}
 
 	/**
@@ -1492,7 +2040,59 @@
 
 		if (!hasNumber)
 			Arrays.fill(md.wanted, true);
-
 		md.display(name, "MDR");
 	}
-}
+	
+	static class HuffNode {
+		Character ch;
+		int freq;
+		HuffNode left = null, right = null;
+
+		HuffNode(Character ch) {
+			this.ch = ch;
+		}
+
+		public HuffNode(Character ch, HuffNode left, HuffNode right) {
+			this.ch = ch;
+			this.left = left;
+			this.right = right;
+		}
+	}
+	
+	void addHuffmanNode(String bits, char v) {
+		if (root == null) {
+			root = new HuffNode(null, null, null);
+		}
+		HuffNode next = root;
+		int depth = 0;
+		while (!bits.isEmpty()) {
+			depth++;
+			if ((next.left != null || next.right != null) && next.ch != null) {
+				throw new IllegalArgumentException("bad tree:" + v);
+			}
+			char bit = bits.charAt(0);
+			bits = bits.substring(1);
+			if (bit == '0') {
+				if (next.left == null)
+					next.left = new HuffNode(null, null, null);
+				else {
+					if (next.ch != null)
+						throw new IllegalArgumentException("bad tree:" + v);
+				}
+				next = next.left;
+			} else {
+				if (next.right == null)
+					next.right = new HuffNode(null, null, null); 
+				else {
+					if (next.ch != null)
+						throw new IllegalArgumentException("bad tree:" + v);
+				}
+				next = next.right;
+			}
+		}
+		if (next.ch != null || next.left != null || next.right != null)
+			throw new IllegalArgumentException("bad tree:" + v);
+		next.ch = v;
+	}
+	
+}
\ No newline at end of file
