porting in progress
diff --git a/src/prettify/CombinePrefixPattern.java b/src/prettify/CombinePrefixPattern.java
index e75500b..bf05efe 100644
--- a/src/prettify/CombinePrefixPattern.java
+++ b/src/prettify/CombinePrefixPattern.java
@@ -75,7 +75,7 @@
         escapeCharToCodeUnit.put('r', 0xf);
     }
 
-    public static int decodeEscape(String charsetPart) {
+    protected static int decodeEscape(String charsetPart) {
         Integer cc0 = charsetPart.codePointAt(0);
         if (cc0 != 92 /* \\ */) {
             return cc0;
@@ -93,7 +93,7 @@
         }
     }
 
-    public static String encodeEscape(int charCode) {
+    protected static String encodeEscape(int charCode) {
         if (charCode < 0x20) {
             return (charCode < 0x10 ? "\\x0" : "\\x") + Integer.toString(charCode, 16);
         }
@@ -103,7 +103,7 @@
                 ? "\\" + ch : ch;
     }
 
-    public static String caseFoldCharset(String charSet) {
+    protected static String caseFoldCharset(String charSet) {
         String[] charsetParts = Util.match(Pattern.compile("\\\\u[0-9A-Fa-f]{4}"
                 + "|\\\\x[0-9A-Fa-f]{2}"
                 + "|\\\\[0-3][0-7]{0,2}"
@@ -185,7 +185,7 @@
         return Util.join(out);
     }
 
-    public String allowAnywhereFoldCaseAndRenumberGroups(Pattern regex) {
+    protected String allowAnywhereFoldCaseAndRenumberGroups(Pattern regex) {
         // Split into character sets, escape sequences, punctuation strings
         // like ('(', '(?:', ')', '^'), and runs of characters that do not
         // include any of the above.
diff --git a/src/prettify/JTextComponentRowHeader.java b/src/prettify/JTextComponentRowHeader.java
index 1f98259..26a7671 100644
--- a/src/prettify/JTextComponentRowHeader.java
+++ b/src/prettify/JTextComponentRowHeader.java
@@ -48,7 +48,7 @@
     protected final static boolean debug;
 
     static {
-        String debugMode = System.getProperty("SyntaxHighlighterDebugMode");
+        String debugMode = System.getProperty("PrettifyDebugMode");
         debug = debugMode == null || !debugMode.equals("true") ? false : true;
     }
     private static final long serialVersionUID = 1L;
diff --git a/src/prettify/Job.java b/src/prettify/Job.java
index cc3d37f..05ff68f 100644
--- a/src/prettify/Job.java
+++ b/src/prettify/Job.java
@@ -26,6 +26,11 @@
         decorations = new ArrayList<Object>();
     }
 
+    public Job(int basePos, String sourceCode) {
+        this.basePos = basePos;
+        this.sourceCode = sourceCode;
+    }
+
     public int getBasePos() {
         return basePos;
     }
diff --git a/src/prettify/Lang.java b/src/prettify/Lang.java
new file mode 100644
index 0000000..d4565dc
--- /dev/null
+++ b/src/prettify/Lang.java
@@ -0,0 +1,69 @@
+package prettify;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class Lang {
+
+    protected List<List<Object>> shortcutStylePatterns;
+    protected List<List<Object>> fallthroughStylePatterns;
+    protected List<Lang> extendedLangs;
+
+    public Lang() {
+        shortcutStylePatterns = new ArrayList<List<Object>>();
+        fallthroughStylePatterns = new ArrayList<List<Object>>();
+        extendedLangs = new ArrayList<Lang>();
+    }
+
+    public static List<String> getFileExtensions() {
+        return new ArrayList<String>();
+    }
+
+    public List<List<Object>> getShortcutStylePatterns() {
+        List<List<Object>> returnList = new ArrayList<List<Object>>();
+        for (List<Object> shortcutStylePattern : shortcutStylePatterns) {
+            returnList.add(new ArrayList<Object>(shortcutStylePattern));
+        }
+        return returnList;
+    }
+
+    public void setShortcutStylePatterns(List<List<Object>> shortcutStylePatterns) {
+        if (shortcutStylePatterns == null) {
+            this.shortcutStylePatterns = new ArrayList<List<Object>>();
+            return;
+        }
+        List<List<Object>> cloneList = new ArrayList<List<Object>>();
+        for (List<Object> shortcutStylePattern : shortcutStylePatterns) {
+            cloneList.add(new ArrayList<Object>(shortcutStylePattern));
+        }
+        this.shortcutStylePatterns = cloneList;
+    }
+
+    public List<List<Object>> getFallthroughStylePatterns() {
+        List<List<Object>> returnList = new ArrayList<List<Object>>();
+        for (List<Object> fallthroughStylePattern : fallthroughStylePatterns) {
+            returnList.add(new ArrayList<Object>(fallthroughStylePattern));
+        }
+        return returnList;
+    }
+
+    public void setFallthroughStylePatterns(List<List<Object>> fallthroughStylePatterns) {
+        if (fallthroughStylePatterns == null) {
+            this.fallthroughStylePatterns = new ArrayList<List<Object>>();
+            return;
+        }
+        List<List<Object>> cloneList = new ArrayList<List<Object>>();
+        for (List<Object> fallthroughStylePattern : fallthroughStylePatterns) {
+            cloneList.add(new ArrayList<Object>(fallthroughStylePattern));
+        }
+        this.fallthroughStylePatterns = cloneList;
+    }
+
+    public List<Lang> getExtendedLangs() {
+        return new ArrayList<Lang>(extendedLangs);
+    }
+
+    public void setExtendedLangs(List<Lang> extendedLangs) {
+        this.extendedLangs = new ArrayList<Lang>(extendedLangs);
+    }
+}
diff --git a/src/prettify/Prettify.java b/src/prettify/Prettify.java
index cf7ddfb..58c4eb7 100644
--- a/src/prettify/Prettify.java
+++ b/src/prettify/Prettify.java
@@ -13,15 +13,25 @@
 // limitations under the License.
 package prettify;
 
+import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.TreeMap;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import java.util.regex.Pattern;
+import prettify.lang.LangAppollo;
+import prettify.lang.LangClj;
+import prettify.lang.LangCss;
+import prettify.lang.LangGo;
+import prettify.lang.LangProto;
+import prettify.lang.LangScala;
+import prettify.lang.LangSql;
+import prettify.lang.LangVb;
+import prettify.lang.LangWiki;
+import prettify.lang.LangYaml;
 
 /**
  * @fileoverview
@@ -62,6 +72,15 @@
  */
 public class Prettify {
 
+    /**
+     * Indicate whether it is in debug mode or not.
+     */
+    protected final static boolean debug;
+
+    static {
+        String debugMode = System.getProperty("PrettifyDebugMode");
+        debug = debugMode == null || !debugMode.equals("true") ? false : true;
+    }
     // Keyword lists for various languages.
     public static final String FLOW_CONTROL_KEYWORDS = "break,continue,do,else,for,if,return,while";
     public static final String C_KEYWORDS = FLOW_CONTROL_KEYWORDS + "," + "auto,case,char,const,default,"
@@ -192,8 +211,10 @@
             decorateSourceMap.put("regexLiterals", true);
             registerLangHandler(sourceDecorator(decorateSourceMap), Arrays.asList(new String[]{"default-code"}));
 
-            List<List<Object>> shortcutStylePatterns = new ArrayList<List<Object>>();
-            List<List<Object>> fallthroughStylePatterns = new ArrayList<List<Object>>();
+            List<List<Object>> shortcutStylePatterns, fallthroughStylePatterns;
+
+            shortcutStylePatterns = new ArrayList<List<Object>>();
+            fallthroughStylePatterns = new ArrayList<List<Object>>();
             fallthroughStylePatterns.add(Arrays.asList(new Object[]{PR_PLAIN, Pattern.compile("^[^<?]+")}));
             fallthroughStylePatterns.add(Arrays.asList(new Object[]{PR_DECLARATION, Pattern.compile("^<!\\w[^>]*(?:>|$)")}));
             fallthroughStylePatterns.add(Arrays.asList(new Object[]{PR_COMMENT, Pattern.compile("^<\\!--[\\s\\S]*?(?:-\\->|$)")}));
@@ -300,6 +321,17 @@
             fallthroughStylePatterns = new ArrayList<List<Object>>();
             fallthroughStylePatterns.add(Arrays.asList(new Object[]{PR_STRING, Pattern.compile("^[\\s\\S]+")}));
             registerLangHandler(new CreateSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns), Arrays.asList(new String[]{"regex"}));
+
+            register(LangAppollo.class);
+            register(LangClj.class);
+            register(LangCss.class);
+            register(LangGo.class);
+            register(LangProto.class);
+            register(LangScala.class);
+            register(LangSql.class);
+            register(LangVb.class);
+            register(LangWiki.class);
+            register(LangYaml.class);
         } catch (Exception ex) {
             Logger.getLogger(Prettify.class.getName()).log(Level.SEVERE, null, ex);
         }
@@ -311,7 +343,7 @@
      * @param basePos the index of sourceCode within the chunk of source
      *    whose decorations are already present on out.
      */
-    public static void appendDecorations(int basePos, String sourceCode, CreateSimpleLexer langHandler, List<Object> out) {
+    protected static void appendDecorations(int basePos, String sourceCode, CreateSimpleLexer langHandler, List<Object> out) {
         if (sourceCode == null) {
             throw new NullPointerException("argument 'sourceCode' cannot be null");
         }
@@ -372,7 +404,7 @@
          * @param fallthroughStylePatterns patterns that will be tried in
          *   order if the shortcut ones fail.  May have shortcuts.
          */
-        public CreateSimpleLexer(List<List<Object>> shortcutStylePatterns, List<List<Object>> fallthroughStylePatterns) throws Exception {
+        protected CreateSimpleLexer(List<List<Object>> shortcutStylePatterns, List<List<Object>> fallthroughStylePatterns) throws Exception {
             this.fallthroughStylePatterns = fallthroughStylePatterns;
 
             List<List<Object>> allPatterns = new ArrayList<List<Object>>(shortcutStylePatterns);
@@ -497,25 +529,7 @@
                 }
             }
 
-            List<Object> newDecorations = new ArrayList<Object>();
-
-            // use TreeMap to remove entrys with same pos
-            Map<Integer, String> posToStyleMap = new TreeMap<Integer, String>();
-            for (int i = 0, iEnd = decorations.size(); i < iEnd;) {
-                posToStyleMap.put((Integer) decorations.get(i++), (String) decorations.get(i++));
-            }
-            // remove adjacent style
-            String previousStyle = null;
-            for (Integer _pos : posToStyleMap.keySet()) {
-                if (previousStyle != null && previousStyle.equals(posToStyleMap.get(_pos))) {
-                    continue;
-                }
-                newDecorations.add(_pos);
-                newDecorations.add(posToStyleMap.get(_pos));
-                previousStyle = posToStyleMap.get(_pos);
-            }
-
-            job.setDecorations(newDecorations);
+            job.setDecorations(Util.removeDuplicates(decorations));
         }
     }
 
@@ -534,7 +548,7 @@
      * @return a function that examines the source code
      *     in the input job and builds the decoration list.
      */
-    public CreateSimpleLexer sourceDecorator(Map<String, Object> options) throws Exception {
+    protected CreateSimpleLexer sourceDecorator(Map<String, Object> options) throws Exception {
         List<List<Object>> shortcutStylePatterns = new ArrayList<List<Object>>();
         List<List<Object>> fallthroughStylePatterns = new ArrayList<List<Object>>();
         if (options.get("tripleQuotedStrings") != null) {
@@ -634,7 +648,7 @@
         shortcutStylePatterns.add(Arrays.asList(new Object[]{PR_PLAIN,
                     Pattern.compile("^\\s+"),
                     null,
-                    " \r\n\t\\xA0"
+                    " \r\n\t" + Character.toString((char) 0xA0)
                 }));
 
         // TODO(mikesamuel): recognize non-latin letters and numerals in idents
@@ -671,7 +685,7 @@
         return new CreateSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns);
     }
     /** Maps language-specific file extensions to handlers. */
-    protected Map<String, CreateSimpleLexer> langHandlerRegistry = new HashMap<String, CreateSimpleLexer>();
+    protected Map<String, Object> langHandlerRegistry = new HashMap<String, Object>();
 
     /** Register a language handler for the given file extensions.
      * @param handler a function from source code to a list
@@ -689,7 +703,7 @@
      *      } }
      * @param fileExtensions
      */
-    public void registerLangHandler(CreateSimpleLexer handler, List<String> fileExtensions) throws Exception {
+    protected void registerLangHandler(CreateSimpleLexer handler, List<String> fileExtensions) throws Exception {
         for (int i = fileExtensions.size(); --i >= 0;) {
             String ext = fileExtensions.get(i);
             if (langHandlerRegistry.get(ext) == null) {
@@ -700,6 +714,34 @@
         }
     }
 
+    /**
+     * Register language handler. The clazz will not be instantiated
+     * @param clazz the class of the language
+     * @throws Exception cannot instantiate the object using the class,
+     * or language handler with specified extension exist already
+     */
+    public void register(Class<? extends Lang> clazz) throws Exception {
+        if (clazz == null) {
+            throw new NullPointerException("argument 'clazz' cannot be null");
+        }
+        Method getExtensionsMethod = clazz.getMethod("getFileExtensions", (Class<?>[]) null);
+        List<String> fileExtensions = (List<String>) getExtensionsMethod.invoke(null, null);
+        for (int i = fileExtensions.size(); --i >= 0;) {
+            String ext = fileExtensions.get(i);
+            if (langHandlerRegistry.get(ext) == null) {
+                langHandlerRegistry.put(ext, clazz);
+            } else {
+                throw new Exception("cannot override language handler " + ext);
+            }
+        }
+    }
+
+    /**
+     * Get the parser for the extension specified. 
+     * @param extension the file extension, if null, default parser will be returned
+     * @param source the source code
+     * @return the parser
+     */
     public CreateSimpleLexer langHandlerForExtension(String extension, String source) {
         if (!(extension != null && langHandlerRegistry.get(extension) != null)) {
             // Treat it as markup if the first non whitespace character is a < and
@@ -708,6 +750,34 @@
                     ? "default-markup"
                     : "default-code";
         }
-        return langHandlerRegistry.get(extension);
+
+        Object handler = langHandlerRegistry.get(extension);
+        if (handler instanceof CreateSimpleLexer) {
+            return (CreateSimpleLexer) handler;
+        } else {
+            Lang _lang;
+            CreateSimpleLexer _simpleLexer;
+            try {
+                _lang = ((Class<Lang>) handler).newInstance();
+                _simpleLexer = new CreateSimpleLexer(_lang.getShortcutStylePatterns(), _lang.getFallthroughStylePatterns());
+
+                List<Lang> extendedLangs = _lang.getExtendedLangs();
+                for (Lang _extendedLang : extendedLangs) {
+                    register(_extendedLang.getClass());
+                }
+            } catch (Exception ex) {
+                if (debug) {
+                    Logger.getLogger(Prettify.class.getName()).log(Level.SEVERE, null, ex);
+                }
+                return null;
+            }
+
+            List<String> fileExtensions = _lang.getFileExtensions();
+            for (String _extension : fileExtensions) {
+                langHandlerRegistry.put(_extension, _simpleLexer);
+            }
+
+            return _simpleLexer;
+        }
     }
 }
diff --git a/src/prettify/SyntaxHighlighter.java b/src/prettify/SyntaxHighlighter.java
new file mode 100644
index 0000000..a53cf26
--- /dev/null
+++ b/src/prettify/SyntaxHighlighter.java
@@ -0,0 +1,260 @@
+/**
+ * This is part of the Java Prettify.
+ * 
+ * It is distributed under MIT license. See the file 'readme.txt' for
+ * information on usage and redistribution of this file, and for a
+ * DISCLAIMER OF ALL WARRANTIES.
+ * 
+ * @author Chan Wai Shing <cws1989@gmail.com>
+ */
+package prettify;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.List;
+import javax.swing.BorderFactory;
+import javax.swing.JScrollPane;
+
+/**
+ * The syntax highlighter.
+ * @author Chan Wai Shing <cws1989@gmail.com>
+ */
+public class SyntaxHighlighter extends JScrollPane {
+
+    static {
+        // set debug mode
+        System.setProperty("PrettifyDebugMode", "false");
+    }
+    private static final long serialVersionUID = 1L;
+    /**
+     * The script text panel.
+     */
+    protected SyntaxHighlighterPane highlighter;
+    /**
+     * The gutter panel (line number).
+     */
+    protected JTextComponentRowHeader highlighterRowHeader;
+    /**
+     * The theme.
+     */
+    protected Theme theme;
+    /**
+     * The content of the syntax highlighter, null if there is no content so far.
+     */
+    protected String content;
+
+    /**
+     * Constructor.
+     * @param theme the theme for the syntax highlighter
+     */
+    public SyntaxHighlighter(Theme theme) {
+        this(theme, new SyntaxHighlighterPane());
+    }
+
+    /**
+     * Constructor.
+     * @param theme the theme for the syntax highlighter
+     * @param highlighterPane the script text pane of the syntax highlighter
+     */
+    public SyntaxHighlighter(Theme theme, SyntaxHighlighterPane highlighterPane) {
+        super();
+
+        if (theme == null) {
+            throw new NullPointerException("argument 'theme' cannot be null");
+        }
+        if (highlighterPane == null) {
+            throw new NullPointerException("argument 'highlighterPane' cannot be null");
+        }
+
+        this.theme = theme;
+
+        setBorder(null);
+
+        highlighter = highlighterPane;
+        highlighter.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 0));
+        highlighter.setTheme(theme);
+        setViewportView(highlighter);
+
+        highlighterRowHeader = new JTextComponentRowHeader(this, highlighter);
+        theme.setTheme(highlighterRowHeader);
+        setRowHeaderView(highlighterRowHeader);
+    }
+
+    /**
+     * Re-render the script text pane. Invoke this when any change of setting that affect the rendering was made.
+     * This will re-parse the content and set the style.
+     */
+    protected void render() {
+//        if (content != null) {
+//            Parser parser = new Parser();
+//            // stop the change listener on the row header to speed up rendering
+//            highlighterRowHeader.setListenToDocumentUpdate(false);
+//            highlighter.setStyle(parser.parse(brush, htmlScript, content.toCharArray(), 0, content.length()));
+//            // resume the change listener on the row header
+//            highlighterRowHeader.setListenToDocumentUpdate(true);
+//            // notify the row header to update its information related to the SyntaxHighlighterPane
+//            // need to call this because we have stopped the change listener of the row header in previous code
+//            highlighterRowHeader.checkPanelSize();
+//        }
+    }
+
+    /**
+     * Get the SyntaxHighlighterPane (the script text panel).
+     * <p><b>Note: Normally should not operate on the SyntaxHighlighterPane directly.</b></p>
+     * @return the SyntaxHighlighterPane
+     */
+    public SyntaxHighlighterPane getHighlighter() {
+        return highlighter;
+    }
+
+    /**
+     * Get the JTextComponentRowHeader, the line number panel.
+     * <p><b>Note: Normally should not operate on the JTextComponentRowHeader directly.</b></p>
+     * @return the JTextComponentRowHeader
+     */
+    public JTextComponentRowHeader getHighlighterRowHeader() {
+        return highlighterRowHeader;
+    }
+
+    /**
+     * Get current theme.
+     * @return the current theme
+     */
+    public Theme getTheme() {
+        return theme;
+    }
+
+    /**
+     * Set the theme.
+     * <p>
+     * Setting the theme will not re-parse the content, but will clear and apply the new theme on the script text pane.
+     * </p>
+     * @param theme the theme
+     */
+    public void setTheme(Theme theme) {
+        if (theme == null) {
+            throw new NullPointerException("argument 'theme' cannot be null");
+        }
+        if (!this.theme.equals(theme)) {
+            this.theme = theme;
+            highlighter.setTheme(theme);
+            theme.setTheme(highlighterRowHeader);
+        }
+    }
+
+    /**
+     * Set the line number of the first line. E.g. if set 10, the line number will start count from 10 instead of 1.
+     * @param firstLine the line number of the first line
+     */
+    public void setFirstLine(int firstLine) {
+        highlighterRowHeader.setLineNumberOffset(firstLine - 1);
+        highlighter.setLineNumberOffset(firstLine - 1);
+    }
+
+    /**
+     * Get the list of highlighted lines.
+     * @return a copy of the list
+     */
+    public List<Integer> getHighlightedLineList() {
+        return highlighter.getHighlightedLineList();
+    }
+
+    /**
+     * Set highlighted lines. Note that this will clear all previous recorded highlighted lines.
+     * @param highlightedLineList the list that contain the highlighted lines
+     */
+    public void setHighlightedLineList(List<Integer> highlightedLineList) {
+        highlighterRowHeader.setHighlightedLineList(highlightedLineList);
+        highlighter.setHighlightedLineList(highlightedLineList);
+    }
+
+    /**
+     * Add highlighted line.
+     * @param lineNumber the line number to highlight
+     */
+    public void addHighlightedLine(int lineNumber) {
+        highlighterRowHeader.addHighlightedLine(lineNumber);
+        highlighter.addHighlightedLine(lineNumber);
+    }
+
+    /**
+     * Get the visibility of the gutter.
+     * @return true if the gutter is visible, false if not
+     */
+    public boolean isGutterVisible() {
+        return getRowHeader() != null && getRowHeader().getView() != null;
+    }
+
+    /**
+     * Set the visibility of the gutter.
+     * @param visible true to make visible, false to hide it
+     */
+    public void setGutterVisible(boolean visible) {
+        if (visible) {
+            setRowHeaderView(highlighterRowHeader);
+            highlighter.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 0));
+        } else {
+            setRowHeaderView(null);
+            highlighter.setBorder(null);
+        }
+    }
+
+    /**
+     * Get the status of the mouse-over highlight effect. Default is on.
+     * @return true if turned on, false if turned off
+     */
+    public boolean isHighlightOnMouseOver() {
+        return highlighter.isHighlightOnMouseOver();
+    }
+
+    /**
+     * Set turn on the mouse-over highlight effect or not.
+     * If set true, there will be a highlight effect on the line that the mouse cursor currently is pointing on (on the script text panel only, not on the line number panel).
+     * @param highlightWhenMouseOver true to turn on, false to turn off
+     */
+    public void setHighlightOnMouseOver(boolean highlightWhenMouseOver) {
+        highlighter.setHighlightOnMouseOver(highlightWhenMouseOver);
+    }
+
+    /**
+     * Set the content of the syntax highlighter. Better set it last after setting all other settings.
+     * @param file the file to read 
+     */
+    public void setContent(File file) throws IOException {
+        setContent(readFile(file));
+    }
+
+    /**
+     * Set the content of the syntax highlighter. It is better to set other settings first and set this the last.
+     * @param content the content to set
+     */
+    public void setContent(String content) {
+        this.content = content;
+        highlighter.setContent(content);
+        render();
+    }
+
+    /**
+     * Get the string content of a file.
+     * @param file the file to retrieve the content from
+     * @return the string content
+     * @throws IOException error occured, either it is not a valid file or failed to read the file
+     */
+    protected static String readFile(File file) throws IOException {
+        if (file == null) {
+            throw new IOException("argument 'file' cannot be null");
+        }
+        if (!file.isFile()) {
+            throw new IOException("It is not a file.");
+        }
+
+        byte[] buffer = new byte[(int) file.length()];
+
+        FileInputStream fileIn = new FileInputStream(file);
+        fileIn.read(buffer);
+        fileIn.close();
+
+        return new String(buffer);
+    }
+}
diff --git a/src/prettify/SyntaxHighlighterPane.java b/src/prettify/SyntaxHighlighterPane.java
new file mode 100644
index 0000000..82059ec
--- /dev/null
+++ b/src/prettify/SyntaxHighlighterPane.java
@@ -0,0 +1,502 @@
+/**
+ * This is part of the Java Prettify.
+ * 
+ * It is distributed under MIT license. See the file 'readme.txt' for
+ * information on usage and redistribution of this file, and for a
+ * DISCLAIMER OF ALL WARRANTIES.
+ * 
+ * @author Chan Wai Shing <cws1989@gmail.com>
+ */
+package prettify;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Rectangle;
+import java.awt.Shape;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseMotionListener;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.swing.JTextPane;
+import javax.swing.text.AbstractDocument;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.BoxView;
+import javax.swing.text.ComponentView;
+import javax.swing.text.DefaultStyledDocument;
+import javax.swing.text.Element;
+import javax.swing.text.Highlighter;
+import javax.swing.text.IconView;
+import javax.swing.text.JTextComponent;
+import javax.swing.text.LabelView;
+import javax.swing.text.ParagraphView;
+import javax.swing.text.SimpleAttributeSet;
+import javax.swing.text.StyleConstants;
+import javax.swing.text.StyledEditorKit;
+import javax.swing.text.View;
+import javax.swing.text.ViewFactory;
+
+/**
+ * The text pane for displaying the script text.
+ * @author Chan Wai Shing <cws1989@gmail.com>
+ */
+public class SyntaxHighlighterPane extends JTextPane {
+
+    /**
+     * Indicate whether it is in debug mode or not.
+     */
+    protected final static boolean debug;
+
+    static {
+        String debugMode = System.getProperty("PrettifyDebugMode");
+        debug = debugMode == null || !debugMode.equals("true") ? false : true;
+    }
+    private static final long serialVersionUID = 1L;
+    /**
+     * The line number offset. E.g. set offset to 9 will make the first line number to appear at line 1 + 9 = 10
+     */
+    private int lineNumberOffset;
+    /**
+     * The background color of the highlighted line. Default is black.
+     */
+    private Color highlightedBackground;
+    /**
+     * Indicator that indicate to turn on the mouse-over highlight effect or not. See {@link #setHighlightOnMouseOver(boolean)}.
+     */
+    private boolean highlightWhenMouseOver;
+    /**
+     * The list of line numbers that indicate which lines are needed to be highlighted.
+     */
+    protected final List<Integer> highlightedLineList;
+    /**
+     * The highlighter painter used to do the highlight line effect.
+     */
+    protected Highlighter.HighlightPainter highlightPainter;
+    /**
+     * The theme.
+     */
+    protected Theme theme;
+    /**
+     * The style list. see {@link #setStyle(java.util.Map)}.
+     */
+    protected Map<String, List<Object>> styleList;
+    /**
+     * Record the mouse cursor is currently pointing at which line of the document. -1 means not any line.
+     * It is used internally.
+     */
+    protected int mouseOnLine;
+
+    public SyntaxHighlighterPane() {
+        super();
+
+        setEditable(false);
+        //<editor-fold defaultstate="collapsed" desc="editor kit">
+        setEditorKit(new StyledEditorKit() {
+
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            public ViewFactory getViewFactory() {
+                return new ViewFactory() {
+
+                    @Override
+                    public View create(Element elem) {
+                        String kind = elem.getName();
+                        if (kind != null) {
+                            if (kind.equals(AbstractDocument.ContentElementName)) {
+                                return new LabelView(elem) {
+
+                                    @Override
+                                    public int getBreakWeight(int axis, float pos, float len) {
+                                        return 0;
+                                    }
+                                };
+                            } else if (kind.equals(AbstractDocument.ParagraphElementName)) {
+                                return new ParagraphView(elem) {
+
+                                    @Override
+                                    public int getBreakWeight(int axis, float pos, float len) {
+                                        return 0;
+                                    }
+                                };
+                            } else if (kind.equals(AbstractDocument.SectionElementName)) {
+                                return new BoxView(elem, View.Y_AXIS);
+                            } else if (kind.equals(StyleConstants.ComponentElementName)) {
+                                return new ComponentView(elem) {
+
+                                    @Override
+                                    public int getBreakWeight(int axis, float pos, float len) {
+                                        return 0;
+                                    }
+                                };
+                            } else if (kind.equals(StyleConstants.IconElementName)) {
+                                return new IconView(elem);
+                            }
+                        }
+                        return new LabelView(elem) {
+
+                            @Override
+                            public int getBreakWeight(int axis, float pos, float len) {
+                                return 0;
+                            }
+                        };
+                    }
+                };
+            }
+        });
+        //</editor-fold>
+
+        lineNumberOffset = 0;
+
+        //<editor-fold defaultstate="collapsed" desc="highlighter painter">
+        highlightedBackground = Color.black;
+        highlightWhenMouseOver = true;
+        highlightedLineList = new ArrayList<Integer>();
+
+        highlightPainter = new Highlighter.HighlightPainter() {
+
+            @Override
+            public void paint(Graphics g, int p0, int p1, Shape bounds, JTextComponent c) {
+                if (c.getParent() == null) {
+                    return;
+                }
+
+                // get the Y-axis value of the visible area of the text component
+                int startY = Math.abs(c.getY());
+                int endY = startY + c.getParent().getHeight();
+
+                FontMetrics textPaneFontMetrics = g.getFontMetrics(getFont());
+                int textPaneFontHeight = textPaneFontMetrics.getHeight();
+
+                int largerestLineNumber = c.getDocument().getDefaultRootElement().getElementCount();
+
+                g.setColor(highlightedBackground);
+
+                // draw the highlight background to the highlighted line
+                synchronized (highlightedLineList) {
+                    for (Integer lineNumber : highlightedLineList) {
+                        if (lineNumber > largerestLineNumber + lineNumberOffset) {
+                            // skip those line number that out of range
+                            continue;
+                        }
+                        // get the Y-axis value of this highlighted line
+                        int _y = Math.max(0, textPaneFontHeight * (lineNumber - lineNumberOffset - 1));
+                        if (_y > endY || _y + textPaneFontHeight < startY) {
+                            // this line is out of visible area, skip it
+                            continue;
+                        }
+                        // draw the highlighted background
+                        g.fillRect(0, _y, c.getWidth(), textPaneFontHeight);
+                    }
+                }
+
+                // draw the mouse-over-highlight effect
+                if (mouseOnLine != -1) {
+                    if (mouseOnLine <= largerestLineNumber + lineNumberOffset) {
+                        int _y = Math.max(0, textPaneFontHeight * (mouseOnLine - lineNumberOffset - 1));
+                        if (_y < endY && _y + textPaneFontHeight > startY) {
+                            // the line is within the range of visible area
+                            g.fillRect(0, _y, c.getWidth(), textPaneFontHeight);
+                        }
+                    }
+                }
+            }
+        };
+        try {
+            getHighlighter().addHighlight(0, 0, highlightPainter);
+        } catch (BadLocationException ex) {
+            if (debug) {
+                Logger.getLogger(SyntaxHighlighterPane.class.getName()).log(Level.SEVERE, null, ex);
+            }
+        }
+        //</editor-fold>
+
+        mouseOnLine = -1;
+
+        //<editor-fold defaultstate="collapsed" desc="mouse listener">
+        addMouseListener(new MouseAdapter() {
+
+            @Override
+            public void mouseExited(MouseEvent e) {
+                if (!highlightWhenMouseOver) {
+                    return;
+                }
+                mouseOnLine = -1;
+                repaint();
+            }
+        });
+        addMouseMotionListener(new MouseMotionListener() {
+
+            @Override
+            public void mouseDragged(MouseEvent e) {
+            }
+
+            @Override
+            public void mouseMoved(MouseEvent e) {
+                if (!highlightWhenMouseOver) {
+                    return;
+                }
+
+                Element defaultRootElement = getDocument().getDefaultRootElement();
+                // get the position of the document the mouse cursor is pointing
+                int documentOffsetStart = viewToModel(e.getPoint());
+
+                // the line number that the mouse cursor is currently pointing
+                int lineNumber = documentOffsetStart == -1 ? -1 : defaultRootElement.getElementIndex(documentOffsetStart) + 1 + lineNumberOffset;
+                if (lineNumber == defaultRootElement.getElementCount()) {
+                    // if the line number got is the last line, check if the cursor is actually on the line or already below the line
+                    try {
+                        Rectangle rectangle = modelToView(documentOffsetStart);
+                        if (e.getY() > rectangle.y + rectangle.height) {
+                            lineNumber = -1;
+                        }
+                    } catch (BadLocationException ex) {
+                        if (debug) {
+                            Logger.getLogger(SyntaxHighlighterPane.class.getName()).log(Level.SEVERE, null, ex);
+                        }
+                    }
+                }
+
+                // only repaint when the line number changed
+                if (mouseOnLine != lineNumber) {
+                    mouseOnLine = lineNumber;
+                    repaint();
+                }
+            }
+        });
+        //</editor-fold>
+    }
+
+    @Override
+    public void setHighlighter(Highlighter highlighter) {
+        if (highlightPainter != null) {
+            getHighlighter().removeHighlight(highlightPainter);
+            try {
+                highlighter.addHighlight(0, 0, highlightPainter);
+            } catch (BadLocationException ex) {
+                if (debug) {
+                    Logger.getLogger(SyntaxHighlighterPane.class.getName()).log(Level.SEVERE, null, ex);
+                }
+            }
+        }
+        super.setHighlighter(highlighter);
+    }
+
+    /**
+     * Set the content of the syntax highlighter. It is better to set other settings first and set this the last.
+     * @param content the content to set
+     */
+    public void setContent(String content) {
+        String newContent = content == null ? "" : content;
+        DefaultStyledDocument document = (DefaultStyledDocument) getDocument();
+
+        try {
+            document.remove(0, document.getLength());
+            if (theme != null) {
+                document.insertString(0, newContent, theme.getPlain().getAttributeSet());
+            } else {
+                document.insertString(0, newContent, new SimpleAttributeSet());
+            }
+        } catch (BadLocationException ex) {
+            if (debug) {
+                Logger.getLogger(SyntaxHighlighterPane.class.getName()).log(Level.SEVERE, null, ex);
+            }
+        }
+
+        setCaretPosition(0);
+
+        // clear the style list
+        styleList = null;
+    }
+
+    /**
+     * Apply the list of style to the script text pane.
+     * @param styleList the style list
+     */
+    public void setStyle(Map<String, List<Object>> styleList) {
+        if (styleList == null) {
+            this.styleList = new HashMap<String, List<Object>>();
+            return;
+        }
+        // not a deep copy
+        this.styleList = new HashMap<String, List<Object>>(styleList);
+
+        if (theme == null) {
+            return;
+        }
+
+        DefaultStyledDocument document = (DefaultStyledDocument) getDocument();
+        // clear all the existing style
+        document.setCharacterAttributes(0, document.getLength(), theme.getPlain().getAttributeSet(), true);
+
+//        // apply style according to the style list
+//        for (String key : styleList.keySet()) {
+//            List<Object> posList = styleList.get(key);
+//
+//            AttributeSet attributeSet = theme.getStyle(key).getAttributeSet();
+//            for (Object pos : posList) {
+//                document.setCharacterAttributes(pos.getOffset(), pos.getLength(), attributeSet, true);
+//            }
+//        }
+
+        repaint();
+    }
+
+    /**
+     * Get current theme.
+     * @return the current theme
+     */
+    public Theme getTheme() {
+        return theme;
+    }
+
+    /**
+     * Set the theme.
+     * @param theme the theme
+     */
+    public void setTheme(Theme theme) {
+        if (theme == null) {
+            throw new NullPointerException("argument 'theme' cannot be null");
+        }
+        this.theme = theme;
+
+        setFont(theme.getFont());
+        setBackground(theme.getBackground());
+        setHighlightedBackground(theme.getHighlightedBackground());
+
+        if (styleList != null) {
+            setStyle(styleList);
+        }
+    }
+
+    /**
+     * Get the line number offset. E.g. set offset to 9 will make the first line number to appear at line 1 + 9 = 10
+     * @return the offset
+     */
+    public int getLineNumberOffset() {
+        return lineNumberOffset;
+    }
+
+    /**
+     * Set the line number offset. E.g. set offset to 9 will make the first line number to appear at line 1 + 9 = 10
+     * @param offset the offset
+     */
+    public void setLineNumberOffset(int offset) {
+        lineNumberOffset = Math.max(lineNumberOffset, offset);
+        repaint();
+    }
+
+    /**
+     * Get the color of the highlighted background. Default is black.
+     * @return the color
+     */
+    public Color getHighlightedBackground() {
+        return highlightedBackground;
+    }
+
+    /**
+     * Set the color of the highlighted background. Default is black.
+     * @param highlightedBackground the color
+     */
+    public void setHighlightedBackground(Color highlightedBackground) {
+        if (highlightedBackground == null) {
+            throw new NullPointerException("argument 'highlightedBackground' cannot be null");
+        }
+        this.highlightedBackground = highlightedBackground;
+        repaint();
+    }
+
+    /**
+     * Get the status of the mouse-over highlight effect. Default is on.
+     * @return true if turned on, false if turned off
+     */
+    public boolean isHighlightOnMouseOver() {
+        return highlightWhenMouseOver;
+    }
+
+    /**
+     * Set turn on the mouse-over highlight effect or not. Default is on.
+     * If set true, there will be a highlight effect on the line that the mouse cursor currently is pointing on (on the script text panel only, not on the line number panel).
+     * @param highlightWhenMouseOver true to turn on, false to turn off
+     */
+    public void setHighlightOnMouseOver(boolean highlightWhenMouseOver) {
+        this.highlightWhenMouseOver = highlightWhenMouseOver;
+        if (!highlightWhenMouseOver) {
+            mouseOnLine = -1;
+        }
+        repaint();
+    }
+
+    /**
+     * Get the list of highlighted lines.
+     * @return a copy of the list
+     */
+    public List<Integer> getHighlightedLineList() {
+        List<Integer> returnList;
+        synchronized (highlightedLineList) {
+            returnList = new ArrayList<Integer>(highlightedLineList);
+        }
+        return returnList;
+    }
+
+    /**
+     * Set highlighted lines. Note that this will clear all previous recorded highlighted lines.
+     * @param highlightedLineList the list that contain the highlighted lines
+     */
+    public void setHighlightedLineList(List<Integer> highlightedLineList) {
+        synchronized (this.highlightedLineList) {
+            this.highlightedLineList.clear();
+            if (highlightedLineList != null) {
+                this.highlightedLineList.addAll(highlightedLineList);
+            }
+        }
+        repaint();
+    }
+
+    /**
+     * Add highlighted line.
+     * @param lineNumber the line number to highlight
+     */
+    public void addHighlightedLine(int lineNumber) {
+        highlightedLineList.add(lineNumber);
+        repaint();
+    }
+
+    /**
+     * Set the <code>font</code> according to <code>bold</code> and <code>italic</code>.
+     * @param font the font to set
+     * @param bold true to set bold, false not
+     * @param italic true to set italic, false not
+     * @return the font with bold and italic changed, or null if the input <code>font</code> is null
+     */
+    protected static Font setFont(Font font, boolean bold, boolean italic) {
+        if (font == null) {
+            return null;
+        }
+        if ((font.getStyle() & Font.ITALIC) != 0) {
+            if (!bold) {
+                return font.deriveFont(font.getStyle() ^ Font.BOLD);
+            }
+        } else {
+            if (bold) {
+                return font.deriveFont(font.getStyle() | Font.BOLD);
+            }
+        }
+        if ((font.getStyle() & Font.ITALIC) != 0) {
+            if (!italic) {
+                return font.deriveFont(font.getStyle() ^ Font.ITALIC);
+            }
+        } else {
+            if (italic) {
+                return font.deriveFont(font.getStyle() | Font.ITALIC);
+            }
+        }
+        return font;
+    }
+}
diff --git a/src/prettify/Theme.java b/src/prettify/Theme.java
new file mode 100644
index 0000000..cf60dbc
--- /dev/null
+++ b/src/prettify/Theme.java
@@ -0,0 +1,771 @@
+/**
+ * This is part of the Java Prettify.
+ * 
+ * It is distributed under MIT license. See the file 'readme.txt' for
+ * information on usage and redistribution of this file, and for a
+ * DISCLAIMER OF ALL WARRANTIES.
+ * 
+ * @author Chan Wai Shing <cws1989@gmail.com>
+ */
+package prettify;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.swing.text.SimpleAttributeSet;
+import javax.swing.text.StyleConstants;
+
+/**
+ * Theme for the Java Prettify.
+ * <p>To make a new theme, either extending this class of initiate this class and set the parameter by the setter.
+ * For the default value, please refer to the constructor.</p>
+ * @author Chan Wai Shing <cws1989@gmail.com>
+ */
+public class Theme {
+
+    /**
+     * Indicate whether it is in debug mode or not.
+     */
+    protected final static boolean debug;
+
+    static {
+        String debugMode = System.getProperty("PrettifyDebugMode");
+        debug = debugMode == null || !debugMode.equals("true") ? false : true;
+    }
+    /**
+     * The font of the script text.
+     */
+    protected Font font;
+    /**
+     * The background color of the script text area.
+     */
+    protected Color background;
+    /**
+     * The background color of the highlighted line of script text.
+     */
+    protected Color highlightedBackground;
+    /**
+     * Gutter (line number column on the left)
+     */
+    /**
+     * The color of the gutter text.
+     */
+    protected Color gutterText;
+    /**
+     * The color of the border that joint the gutter and the script text panel.
+     */
+    protected Color gutterBorderColor;
+    /**
+     * The width of the border that joint the gutter and the script text panel.
+     */
+    protected int gutterBorderWidth;
+    /**
+     * The font of the gutter text.
+     */
+    protected Font gutterTextFont;
+    /**
+     * The minimum padding from 'the leftmost of the line number text' to 'the left margin'.
+     */
+    protected int gutterTextPaddingLeft;
+    /**
+     * The minimum padding from 'the rightmost of the line number text' to 'the right margin' (not to the gutter border).
+     */
+    protected int gutterTextPaddingRight;
+    protected Style string;
+    protected Style keyword;
+    protected Style comment;
+    protected Style type;
+    protected Style literal;
+    protected Style punctuation;
+    protected Style plain;
+    protected Style tag;
+    protected Style declaration;
+    protected Style source;
+    protected Style attributeName;
+    protected Style attributeValue;
+    protected Style noCode;
+    protected Style openBracket;
+    protected Style closeBracket;
+    protected Style variable;
+    protected Style function;
+
+    /**
+     * Constructor.<br />
+     * <p>
+     * <b>Default value:</b><br />
+     * <ul>
+     * <li>font: Consolas 12pt</li>
+     * <li>background: white</li>
+     * <li>gutter text: black</li>
+     * <li>gutter border: R: 184, G: 184, B: 184</li>
+     * <li>gutter border width: 3px</li>
+     * <li>gutter text font: Consolas 12pt</li>
+     * <li>gutter text padding-left: 7px</li>
+     * <li>gutter text padding-right: 7px</li>
+     * <li>plain, comments, string, keyword, preprocessor, variable, value, functions, constants, script, scriptBackground, color1, color2, color3: no style set</li>
+     * </ul>
+     * </p>
+     */
+    public Theme() {
+        font = new Font("Consolas", Font.PLAIN, 12);
+        background = Color.white;
+
+        highlightedBackground = Color.gray;
+
+        gutterText = Color.black;
+        gutterBorderColor = new Color(184, 184, 184);
+        gutterBorderWidth = 3;
+        gutterTextFont = new Font("Consolas", Font.PLAIN, 12);
+        gutterTextPaddingLeft = 7;
+        gutterTextPaddingRight = 7;
+
+        string = new Style();
+        keyword = new Style();
+        comment = new Style();
+        type = new Style();
+        literal = new Style();
+        punctuation = new Style();
+        plain = new Style();
+        tag = new Style();
+        declaration = new Style();
+        source = new Style();
+        attributeName = new Style();
+        attributeValue = new Style();
+        noCode = new Style();
+        openBracket = new Style();
+        closeBracket = new Style();
+        variable = new Style();
+        function = new Style();
+    }
+
+    /**
+     * Apply the theme to the row header panel.
+     * @param rowHeader the row header to apply theme on
+     */
+    public void setTheme(JTextComponentRowHeader rowHeader) {
+        rowHeader.setBackground(background);
+        rowHeader.setHighlightedColor(background);
+
+        rowHeader.setForeground(gutterText);
+        rowHeader.setBorderColor(gutterBorderColor);
+        rowHeader.setBorderWidth(gutterBorderWidth);
+        rowHeader.setFont(gutterTextFont);
+        rowHeader.setPaddingLeft(gutterTextPaddingLeft);
+        rowHeader.setPaddingRight(gutterTextPaddingRight);
+    }
+
+    /**
+     * Get the {@link prettify.Theme.Style} by keyword.
+     * @param key the keyword, possible values: plain, comments, string, keyword, preprocessor, variable, value, functions, constants, script, scriptBackground, color1, color2 or color3
+     * @return the {@link prettify.Theme.Style} related to the {@code key}; if the style related to the <code>key</code> not exist, the style of 'plain' will return.
+     */
+    public Style getStyle(String key) {
+        if (key.equals("str")) {
+            return string;
+        } else if (key.equals("kwd")) {
+            return keyword;
+        } else if (key.equals("com")) {
+            return comment;
+        } else if (key.equals("typ")) {
+            return type;
+        } else if (key.equals("lit")) {
+            return literal;
+        } else if (key.equals("pub")) {
+            return punctuation;
+        } else if (key.equals("pln")) {
+            return plain;
+        } else if (key.equals("tag")) {
+            return tag;
+        } else if (key.equals("dec")) {
+            return declaration;
+        } else if (key.equals("src")) {
+            return source;
+        } else if (key.equals("atn")) {
+            return attributeName;
+        } else if (key.equals("atv")) {
+            return attributeValue;
+        } else if (key.equals("nocode")) {
+            return noCode;
+        } else if (key.equals("opn")) {
+            return openBracket;
+        } else if (key.equals("clo")) {
+            return closeBracket;
+        } else if (key.equals("var")) {
+            return variable;
+        } else if (key.equals("fun")) {
+            return function;
+        } else {
+            // key not exist
+            return plain;
+        }
+    }
+
+    /**
+     * The font of the script text.
+     */
+    public Font getFont() {
+        return font;
+    }
+
+    /**
+     * The font of the script text.
+     */
+    public void setFont(Font font) {
+        if (font == null) {
+            throw new NullPointerException("argument 'font' cannot be null");
+        }
+        this.font = font;
+    }
+
+    /**
+     * The background color of the script text area.
+     */
+    public Color getBackground() {
+        return background;
+    }
+
+    /**
+     * The background color of the script text area.
+     */
+    public void setBackground(Color background) {
+        if (background == null) {
+            throw new NullPointerException("argument 'background' cannot be null");
+        }
+        this.background = background;
+    }
+
+    /**
+     * The background color of the highlighted line of script text.
+     */
+    public Color getHighlightedBackground() {
+        return highlightedBackground;
+    }
+
+    /**
+     * The background color of the highlighted line of script text.
+     */
+    public void setHighlightedBackground(Color highlightedBackground) {
+        if (highlightedBackground == null) {
+            throw new NullPointerException("argument 'highlightedBackground' cannot be null");
+        }
+        this.highlightedBackground = highlightedBackground;
+    }
+
+    /**
+     * The color of the gutter text.
+     */
+    public Color getGutterText() {
+        return gutterText;
+    }
+
+    /**
+     * The color of the gutter text.
+     */
+    public void setGutterText(Color gutterText) {
+        if (gutterText == null) {
+            throw new NullPointerException("argument 'gutterText' cannot be null");
+        }
+        this.gutterText = gutterText;
+    }
+
+    /**
+     * The color of the border that joint the gutter and the script text panel.
+     */
+    public Color getGutterBorderColor() {
+        return gutterBorderColor;
+    }
+
+    /**
+     * The color of the border that joint the gutter and the script text panel.
+     */
+    public void setGutterBorderColor(Color gutterBorderColor) {
+        if (gutterBorderColor == null) {
+            throw new NullPointerException("argument 'gutterBorderColor' cannot be null");
+        }
+        this.gutterBorderColor = gutterBorderColor;
+    }
+
+    /**
+     * The width of the border that joint the gutter and the script text panel.
+     */
+    public int getGutterBorderWidth() {
+        return gutterBorderWidth;
+    }
+
+    /**
+     * The width of the border that joint the gutter and the script text panel.
+     */
+    public void setGutterBorderWidth(int gutterBorderWidth) {
+        this.gutterBorderWidth = gutterBorderWidth;
+    }
+
+    /**
+     * The font of the gutter text.
+     */
+    public Font getGutterTextFont() {
+        return gutterTextFont;
+    }
+
+    /**
+     * The font of the gutter text.
+     */
+    public void setGutterTextFont(Font gutterTextFont) {
+        if (gutterTextFont == null) {
+            throw new NullPointerException("argument 'gutterTextFont' cannot be null");
+        }
+        this.gutterTextFont = gutterTextFont;
+    }
+
+    /**
+     * The minimum padding from 'the leftmost of the line number text' to 'the left margin'.
+     */
+    public int getGutterTextPaddingLeft() {
+        return gutterTextPaddingLeft;
+    }
+
+    /**
+     * The minimum padding from 'the leftmost of the line number text' to 'the left margin'.
+     */
+    public void setGutterTextPaddingLeft(int gutterTextPaddingLeft) {
+        this.gutterTextPaddingLeft = gutterTextPaddingLeft;
+    }
+
+    /**
+     * The minimum padding from 'the rightmost of the line number text' to 'the right margin' (not to the gutter border).
+     */
+    public int getGutterTextPaddingRight() {
+        return gutterTextPaddingRight;
+    }
+
+    /**
+     * The minimum padding from 'the rightmost of the line number text' to 'the right margin' (not to the gutter border).
+     */
+    public void setGutterTextPaddingRight(int gutterTextPaddingRight) {
+        this.gutterTextPaddingRight = gutterTextPaddingRight;
+    }
+
+    public Style getString() {
+        return string;
+    }
+
+    public void setString(Style string) {
+        this.string = string;
+    }
+
+    public Style getKeyword() {
+        return keyword;
+    }
+
+    public void setKeyword(Style keyword) {
+        this.keyword = keyword;
+    }
+
+    public Style getComment() {
+        return comment;
+    }
+
+    public void setComment(Style comment) {
+        this.comment = comment;
+    }
+
+    public Style getType() {
+        return type;
+    }
+
+    public void setType(Style type) {
+        this.type = type;
+    }
+
+    public Style getLiteral() {
+        return literal;
+    }
+
+    public void setLiteral(Style literal) {
+        this.literal = literal;
+    }
+
+    public Style getPunctuation() {
+        return punctuation;
+    }
+
+    public void setPunctuation(Style punctuation) {
+        this.punctuation = punctuation;
+    }
+
+    public Style getPlain() {
+        return plain;
+    }
+
+    public void setPlain(Style plain) {
+        this.plain = plain;
+    }
+
+    public Style getTag() {
+        return tag;
+    }
+
+    public void setTag(Style tag) {
+        this.tag = tag;
+    }
+
+    public Style getDeclaration() {
+        return declaration;
+    }
+
+    public void setDeclaration(Style declaration) {
+        this.declaration = declaration;
+    }
+
+    public Style getSource() {
+        return source;
+    }
+
+    public void setSource(Style source) {
+        this.source = source;
+    }
+
+    public Style getAttributeName() {
+        return attributeName;
+    }
+
+    public void setAttributeName(Style attributeName) {
+        this.attributeName = attributeName;
+    }
+
+    public Style getAttributeValue() {
+        return attributeValue;
+    }
+
+    public void setAttributeValue(Style attributeValue) {
+        this.attributeValue = attributeValue;
+    }
+
+    public Style getNoCode() {
+        return noCode;
+    }
+
+    public void setNoCode(Style noCode) {
+        this.noCode = noCode;
+    }
+
+    public Style getOpenBracket() {
+        return openBracket;
+    }
+
+    public void setOpenBracket(Style openBracket) {
+        this.openBracket = openBracket;
+    }
+
+    public Style getCloseBracket() {
+        return closeBracket;
+    }
+
+    public void setCloseBracket(Style closeBracket) {
+        this.closeBracket = closeBracket;
+    }
+
+    public Style getVariable() {
+        return variable;
+    }
+
+    public void setVariable(Style variable) {
+        this.variable = variable;
+    }
+
+    public Style getFunction() {
+        return function;
+    }
+
+    public void setFunction(Style function) {
+        this.function = function;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Theme clone() {
+        Theme object = null;
+        try {
+            object = (Theme) super.clone();
+            object.font = font;
+            object.background = background;
+            object.highlightedBackground = highlightedBackground;
+            object.gutterText = gutterText;
+            object.gutterBorderColor = gutterBorderColor;
+            object.gutterBorderWidth = gutterBorderWidth;
+            object.gutterTextFont = gutterTextFont;
+            object.gutterTextPaddingLeft = gutterTextPaddingLeft;
+            object.gutterTextPaddingRight = gutterTextPaddingRight;
+            object.string = string.clone();
+            object.keyword = keyword.clone();
+            object.comment = comment.clone();
+            object.type = type.clone();
+            object.literal = literal.clone();
+            object.punctuation = punctuation.clone();
+            object.plain = plain.clone();
+            object.tag = tag.clone();
+            object.declaration = declaration.clone();
+            object.source = source.clone();
+            object.attributeName = attributeName.clone();
+            object.attributeValue = attributeValue.clone();
+            object.noCode = noCode.clone();
+            object.openBracket = openBracket.clone();
+            object.closeBracket = closeBracket.clone();
+            object.variable = variable.clone();
+            object.function = function.clone();
+        } catch (CloneNotSupportedException ex) {
+            if (debug) {
+                Logger.getLogger(Theme.class.getName()).log(Level.WARNING, null, ex);
+            }
+        }
+        return object;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+
+        sb.append(getClass().getName());
+        sb.append(": ");
+        sb.append("font: ").append(getFont());
+        sb.append(", ");
+        sb.append("background: ").append(getBackground());
+        sb.append(", ");
+        sb.append("highlightedBackground: ").append(getHighlightedBackground());
+        sb.append(", ");
+        sb.append("gutterText: ").append(getGutterText());
+        sb.append(", ");
+        sb.append("gutterBorderColor: ").append(getGutterBorderColor());
+        sb.append(", ");
+        sb.append("gutterBorderWidth: ").append(getGutterBorderWidth());
+        sb.append(", ");
+        sb.append("gutterTextFont: ").append(getGutterTextFont());
+        sb.append(", ");
+        sb.append("gutterTextPaddingLeft: ").append(getGutterTextPaddingLeft());
+        sb.append(", ");
+        sb.append("gutterTextPaddingRight: ").append(getGutterTextPaddingRight());
+        sb.append(", ");
+        sb.append("string: ").append(getString());
+        sb.append(", ");
+        sb.append("keyword: ").append(getKeyword());
+        sb.append(", ");
+        sb.append("comment: ").append(getComment());
+        sb.append(", ");
+        sb.append("type: ").append(getType());
+        sb.append(", ");
+        sb.append("literal: ").append(getLiteral());
+        sb.append(", ");
+        sb.append("punctuation: ").append(getPunctuation());
+        sb.append(", ");
+        sb.append("plain: ").append(getPlain());
+        sb.append(", ");
+        sb.append("tag: ").append(getTag());
+        sb.append(", ");
+        sb.append("declaration: ").append(getDeclaration());
+        sb.append(", ");
+        sb.append("source: ").append(getSource());
+        sb.append(", ");
+        sb.append("attributeName: ").append(getAttributeName());
+        sb.append(", ");
+        sb.append("attributeValue: ").append(getAttributeValue());
+        sb.append(", ");
+        sb.append("noCode: ").append(getNoCode());
+        sb.append(", ");
+        sb.append("openBracket: ").append(getOpenBracket());
+        sb.append(", ");
+        sb.append("closeBracket: ").append(getCloseBracket());
+        sb.append(", ");
+        sb.append("variable: ").append(getVariable());
+        sb.append(", ");
+        sb.append("function: ").append(getFunction());
+
+        return sb.toString();
+    }
+
+    /**
+     * The style used by {@link prettify.theme} as those of CSS styles.
+     */
+    public static class Style {
+
+        protected boolean bold;
+        protected Color color;
+        /**
+         * The background color, null means no background color is set.
+         */
+        protected Color background;
+        protected boolean underline;
+        protected boolean italic;
+
+        /**
+         * Constructor.
+         * <p>
+         * <b>Default values:</b><br />
+         * <ul>
+         * <li>bold: false;</li>
+         * <li>color: black;</li>
+         * <li>background: null;</li>
+         * <li>underline: false;</li>
+         * <li>italic: false;</li>
+         * </ul>
+         * </p>
+         */
+        public Style() {
+            bold = false;
+            color = Color.black;
+            background = null;
+            underline = false;
+            italic = false;
+        }
+
+        /**
+         * Apply the style to the AttributeSet.
+         * Note that the AttributeSet should only be set by this function once or some unexpected style may appear.
+         * @param attributeSet the AttributeSet to set the style on
+         */
+        public void setAttributeSet(SimpleAttributeSet attributeSet) {
+            if (attributeSet == null) {
+                return;
+            }
+            StyleConstants.setBold(attributeSet, bold);
+            StyleConstants.setForeground(attributeSet, color);
+            if (background != null) {
+                StyleConstants.setBackground(attributeSet, background);
+            } else {
+                attributeSet.removeAttribute(StyleConstants.Background);
+            }
+            StyleConstants.setUnderline(attributeSet, underline);
+            StyleConstants.setItalic(attributeSet, italic);
+        }
+
+        /**
+         * Get the AttributeSet from this style.
+         * @return the AttributeSet
+         */
+        public SimpleAttributeSet getAttributeSet() {
+            SimpleAttributeSet attributeSet = new SimpleAttributeSet();
+            StyleConstants.setBold(attributeSet, bold);
+            StyleConstants.setForeground(attributeSet, color);
+            if (background != null) {
+                StyleConstants.setBackground(attributeSet, background);
+            }
+            StyleConstants.setUnderline(attributeSet, underline);
+            StyleConstants.setItalic(attributeSet, italic);
+            return attributeSet;
+        }
+
+        /**
+         * Get the background color.
+         * @return the background color or null if no color is set
+         */
+        public Color getBackground() {
+            return background;
+        }
+
+        /**
+         * Set the background color.
+         * @param background input null means do not set the background
+         */
+        public void setBackground(Color background) {
+            if (background == null) {
+                throw new NullPointerException("argument 'background' cannot be null");
+            }
+            this.background = background;
+        }
+
+        public boolean isBold() {
+            return bold;
+        }
+
+        public void setBold(boolean bold) {
+            this.bold = bold;
+        }
+
+        public Color getColor() {
+            return color;
+        }
+
+        public void setColor(Color color) {
+            if (color == null) {
+                throw new NullPointerException("argument 'color' cannot be null");
+            }
+            this.color = color;
+        }
+
+        public boolean isItalic() {
+            return italic;
+        }
+
+        public void setItalic(boolean italic) {
+            this.italic = italic;
+        }
+
+        public boolean isUnderline() {
+            return underline;
+        }
+
+        public void setUnderline(boolean underline) {
+            this.underline = underline;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public boolean equals(Object obj) {
+            if (obj == null || !(obj instanceof Style)) {
+                return false;
+            }
+            if (obj == this) {
+                return true;
+            }
+            Style _object = (Style) obj;
+            return _object.bold == bold && _object.color.equals(color) && _object.background.equals(background)
+                    && _object.underline == underline && _object.italic == italic;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public Style clone() {
+            Style object = null;
+            try {
+                object = (Style) super.clone();
+                object.bold = bold;
+                object.color = color;
+                object.background = background;
+                object.underline = underline;
+                object.italic = italic;
+            } catch (CloneNotSupportedException ex) {
+            }
+            return object;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+
+            sb.append(getClass().getName());
+            sb.append(": ");
+            sb.append("bold: ").append(bold);
+            sb.append(", ");
+            sb.append("color: ").append(color);
+            sb.append(", ");
+            sb.append("bg: ").append(background);
+            sb.append(", ");
+            sb.append("underline: ").append(underline);
+            sb.append(", ");
+            sb.append("italic: ").append(italic);
+
+            return sb.toString();
+        }
+    }
+}
diff --git a/src/prettify/Util.java b/src/prettify/Util.java
index d4afa9c..b8c69e9 100644
--- a/src/prettify/Util.java
+++ b/src/prettify/Util.java
@@ -2,6 +2,8 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -48,4 +50,27 @@
         }
         return sb.toString();
     }
+
+    public static List<Object> removeDuplicates(List<Object> decorations) {
+        // use TreeMap to remove entrys with same pos
+        Map<Integer, Object> orderedMap = new TreeMap<Integer, Object>();
+        for (int i = 0, iEnd = decorations.size(); i < iEnd; i++) {
+            orderedMap.put((Integer) decorations.get(i), decorations.get(i + 1));
+            i++;
+        }
+
+        // remove adjacent style
+        List<Object> returnList = new ArrayList<Object>(orderedMap.size() + 1);
+        String previousStyle = null;
+        for (Integer _pos : orderedMap.keySet()) {
+            if (previousStyle != null && previousStyle.equals(orderedMap.get(_pos))) {
+                continue;
+            }
+            returnList.add(_pos);
+            returnList.add(orderedMap.get(_pos));
+            previousStyle = (String) orderedMap.get(_pos);
+        }
+
+        return returnList;
+    }
 }
diff --git a/src/prettify/lang/LangAppollo.java b/src/prettify/lang/LangAppollo.java
new file mode 100644
index 0000000..15ac802
--- /dev/null
+++ b/src/prettify/lang/LangAppollo.java
@@ -0,0 +1,63 @@
+// Copyright (C) 2009 Onno Hommes.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package prettify.lang;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Pattern;
+import prettify.Lang;
+import prettify.Prettify;
+
+/**
+ * @fileoverview
+ * Registers a language handler for the AGC/AEA Assembly Language as described
+ * at http://virtualagc.googlecode.com
+ * <p>
+ * This file could be used by goodle code to allow syntax highlight for
+ * Virtual AGC SVN repository or if you don't want to commonize
+ * the header for the agc/aea html assembly listing.
+ *
+ * @author ohommes@alumni.cmu.edu
+ */
+public class LangAppollo extends Lang {
+
+    public LangAppollo() {
+        List<List<Object>> _shortcutStylePatterns = new ArrayList<List<Object>>();
+        List<List<Object>> _fallthroughStylePatterns = new ArrayList<List<Object>>();
+
+        // A line comment that starts with ;
+        _shortcutStylePatterns.add(Arrays.asList(new Object[]{Prettify.PR_COMMENT, Pattern.compile("^#[^\r\n]*"), null, "#"}));
+        // Whitespace
+        _shortcutStylePatterns.add(Arrays.asList(new Object[]{Prettify.PR_PLAIN, Pattern.compile("^[\t\n\r \\xA0]+"), null, "\t\n\r " + Character.toString((char) 0xA0)}));
+        // A double quoted, possibly multi-line, string.
+        _shortcutStylePatterns.add(Arrays.asList(new Object[]{Prettify.PR_STRING, Pattern.compile("^\\\"(?:[^\\\"\\\\]|\\\\[\\s\\S])*(?:\\\"|$)"), null, "\""}));
+        _fallthroughStylePatterns.add(Arrays.asList(new Object[]{Prettify.PR_KEYWORD, Pattern.compile("^(?:ADS|AD|AUG|BZF|BZMF|CAE|CAF|CA|CCS|COM|CS|DAS|DCA|DCOM|DCS|DDOUBL|DIM|DOUBLE|DTCB|DTCF|DV|DXCH|EDRUPT|EXTEND|INCR|INDEX|NDX|INHINT|LXCH|MASK|MSK|MP|MSU|NOOP|OVSK|QXCH|RAND|READ|RELINT|RESUME|RETURN|ROR|RXOR|SQUARE|SU|TCR|TCAA|OVSK|TCF|TC|TS|WAND|WOR|WRITE|XCH|XLQ|XXALQ|ZL|ZQ|ADD|ADZ|SUB|SUZ|MPY|MPR|MPZ|DVP|COM|ABS|CLA|CLZ|LDQ|STO|STQ|ALS|LLS|LRS|TRA|TSQ|TMI|TOV|AXT|TIX|DLY|INP|OUT)\\s"), null}));
+        _fallthroughStylePatterns.add(Arrays.asList(new Object[]{Prettify.PR_TYPE, Pattern.compile("^(?:-?GENADR|=MINUS|2BCADR|VN|BOF|MM|-?2CADR|-?[1-6]DNADR|ADRES|BBCON|[SE]?BANK\\=?|BLOCK|BNKSUM|E?CADR|COUNT\\*?|2?DEC\\*?|-?DNCHAN|-?DNPTR|EQUALS|ERASE|MEMORY|2?OCT|REMADR|SETLOC|SUBRO|ORG|BSS|BES|SYN|EQU|DEFINE|END)\\s"), null}));
+        // A single quote possibly followed by a word that optionally ends with
+        // = ! or ?.
+        _fallthroughStylePatterns.add(Arrays.asList(new Object[]{Prettify.PR_LITERAL, Pattern.compile("^\\'(?:-*(?:\\w|\\\\[\\x21-\\x7e])(?:[\\w-]*|\\\\[\\x21-\\x7e])[=!?]?)?")}));
+        // Any word including labels that optionally ends with = ! or ?.
+        _fallthroughStylePatterns.add(Arrays.asList(new Object[]{Prettify.PR_PLAIN, Pattern.compile("^-*(?:[!-z_]|\\\\[\\x21-\\x7e])(?:[\\w-]*|\\\\[\\x21-\\x7e])[=!?]?", Pattern.CASE_INSENSITIVE)}));
+        // A printable non-space non-special character
+        _fallthroughStylePatterns.add(Arrays.asList(new Object[]{Prettify.PR_PUNCTUATION, Pattern.compile("^[^\\w\\t\\n\\r \\xA0()\\\"\\\\\\';]+")}));
+
+        setShortcutStylePatterns(_shortcutStylePatterns);
+        setFallthroughStylePatterns(_fallthroughStylePatterns);
+    }
+
+    public static List<String> getExtensions() {
+        return Arrays.asList(new String[]{"apollo", "agc", "aea"});
+    }
+}
diff --git a/src/prettify/lang/LangClj.java b/src/prettify/lang/LangClj.java
new file mode 100644
index 0000000..8765cf0
--- /dev/null
+++ b/src/prettify/lang/LangClj.java
@@ -0,0 +1,79 @@
+/**
+ * @license Copyright (C) 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package prettify.lang;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Pattern;
+import prettify.Lang;
+import prettify.Prettify;
+
+/**
+ * @fileoverview
+ * Registers a language handler for Clojure.
+ *
+ *
+ * To use, include prettify.js and this file in your HTML page.
+ * Then put your code in an HTML tag like
+ *      <pre class="prettyprint lang-lisp">(my lisp code)</pre>
+ * The lang-cl class identifies the language as common lisp.
+ * This file supports the following language extensions:
+ *     lang-clj - Clojure
+ *
+ *
+ * I used lang-lisp.js as the basis for this adding the clojure specific
+ * keywords and syntax.
+ *
+ * "Name"    = 'Clojure'
+ * "Author"  = 'Rich Hickey'
+ * "Version" = '1.2'
+ * "About"   = 'Clojure is a lisp for the jvm with concurrency primitives and a richer set of types.'
+ *
+ *
+ * I used <a href="http://clojure.org/Reference">Clojure.org Reference</a> as
+ * the basis for the reserved word list.
+ *
+ *
+ * @author jwall@google.com
+ */
+public class LangClj extends Lang {
+
+    public LangClj() {
+        List<List<Object>> _shortcutStylePatterns = new ArrayList<List<Object>>();
+        List<List<Object>> _fallthroughStylePatterns = new ArrayList<List<Object>>();
+
+        // clojure has more paren types than minimal lisp.
+        _shortcutStylePatterns.add(Arrays.asList(new Object[]{"opn", Pattern.compile("^[\\(\\{\\[]+"), null, "([{"}));
+        _shortcutStylePatterns.add(Arrays.asList(new Object[]{"clo", Pattern.compile("^[\\)\\}\\]]+"), null, ")]}"}));
+        // A line comment that starts with ;
+        _shortcutStylePatterns.add(Arrays.asList(new Object[]{Prettify.PR_COMMENT, Pattern.compile("^;[^\r\n]*"), null, ";"}));
+        // Whitespace
+        _shortcutStylePatterns.add(Arrays.asList(new Object[]{Prettify.PR_PLAIN, Pattern.compile("^[\t\n\r \\xA0]+"), null, "\t\n\r " + Character.toString((char) 0xA0)}));
+        // A double quoted, possibly multi-line, string.
+        _shortcutStylePatterns.add(Arrays.asList(new Object[]{Prettify.PR_STRING, Pattern.compile("^\\\"(?:[^\\\"\\\\]|\\\\[\\s\\S])*(?:\\\"|$)"), null, "\""}));
+        // clojure has a much larger set of keywords
+        _fallthroughStylePatterns.add(Arrays.asList(new Object[]{Prettify.PR_KEYWORD, Pattern.compile("^(?:def|if|do|let|quote|var|fn|loop|recur|throw|try|monitor-enter|monitor-exit|defmacro|defn|defn-|macroexpand|macroexpand-1|for|doseq|dosync|dotimes|and|or|when|not|assert|doto|proxy|defstruct|first|rest|cons|defprotocol|deftype|defrecord|reify|defmulti|defmethod|meta|with-meta|ns|in-ns|create-ns|import|intern|refer|alias|namespace|resolve|ref|deref|refset|new|set!|memfn|to-array|into-array|aset|gen-class|reduce|map|filter|find|nil?|empty?|hash-map|hash-set|vec|vector|seq|flatten|reverse|assoc|dissoc|list|list?|disj|get|union|difference|intersection|extend|extend-type|extend-protocol|prn)\\b"), null}));
+        _fallthroughStylePatterns.add(Arrays.asList(new Object[]{Prettify.PR_TYPE, Pattern.compile("^:[0-9a-zA-Z\\-]+")}));
+
+        setShortcutStylePatterns(_shortcutStylePatterns);
+        setFallthroughStylePatterns(_fallthroughStylePatterns);
+    }
+
+    public static List<String> getExtensions() {
+        return Arrays.asList(new String[]{"clj"});
+    }
+}
diff --git a/src/prettify/lang/LangCss.java b/src/prettify/lang/LangCss.java
new file mode 100644
index 0000000..d39c668
--- /dev/null
+++ b/src/prettify/lang/LangCss.java
@@ -0,0 +1,109 @@
+// Copyright (C) 2009 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package prettify.lang;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Pattern;
+import prettify.Lang;
+import prettify.Prettify;
+
+/**
+ * @fileoverview
+ * Registers a language handler for CSS.
+ *
+ *
+ * To use, include prettify.js and this file in your HTML page.
+ * Then put your code in an HTML tag like
+ *      <pre class="prettyprint lang-css"></pre>
+ *
+ *
+ * http://www.w3.org/TR/CSS21/grammar.html Section G2 defines the lexical
+ * grammar.  This scheme does not recognize keywords containing escapes.
+ *
+ * @author mikesamuel@gmail.com
+ */
+public class LangCss extends Lang {
+
+    public LangCss() {
+        List<List<Object>> _shortcutStylePatterns = new ArrayList<List<Object>>();
+        List<List<Object>> _fallthroughStylePatterns = new ArrayList<List<Object>>();
+
+        // The space production <s>
+        _shortcutStylePatterns.add(Arrays.asList(new Object[]{Prettify.PR_PLAIN, Pattern.compile("^[ \t\r\n\f]+"), null, " \t\r\n\f"}));
+        // Quoted strings.  <string1> and <string2>
+        _fallthroughStylePatterns.add(Arrays.asList(new Object[]{Prettify.PR_STRING, Pattern.compile("^\\\"(?:[^\n\r\f\\\\\\\"]|\\\\(?:\r\n?|\n|\f)|\\\\[\\s\\S])*\\\""), null}));
+        _fallthroughStylePatterns.add(Arrays.asList(new Object[]{Prettify.PR_STRING, Pattern.compile("^\\'(?:[^\n\r\f\\\\\\']|\\\\(?:\r\n?|\n|\f)|\\\\[\\s\\S])*\\'"), null}));
+        _fallthroughStylePatterns.add(Arrays.asList(new Object[]{"lang-css-str", Pattern.compile("^url\\(([^\\)\\\"\\']*)\\)", Pattern.CASE_INSENSITIVE)}));
+        _fallthroughStylePatterns.add(Arrays.asList(new Object[]{Prettify.PR_KEYWORD, Pattern.compile("^(?:url|rgb|\\!important|@import|@page|@media|@charset|inherit)(?=[^\\-\\w]|$)", Pattern.CASE_INSENSITIVE), null}));
+        // A property name -- an identifier followed by a colon.
+        _fallthroughStylePatterns.add(Arrays.asList(new Object[]{"lang-css-kw", Pattern.compile("^(-?(?:[_a-z]|(?:\\\\[0-9a-f]+ ?))(?:[_a-z0-9\\-]|\\\\(?:\\\\[0-9a-f]+ ?))*)\\s*:", Pattern.CASE_INSENSITIVE)}));
+        // A C style block comment.  The <comment> production.
+        _fallthroughStylePatterns.add(Arrays.asList(new Object[]{Prettify.PR_COMMENT, Pattern.compile("^\\/\\*[^*]*\\*+(?:[^\\/*][^*]*\\*+)*\\/")}));
+        // Escaping text spans
+        _fallthroughStylePatterns.add(Arrays.asList(new Object[]{Prettify.PR_COMMENT, Pattern.compile("^(?:<!--|-->)/")}));
+        // A number possibly containing a suffix.
+        _fallthroughStylePatterns.add(Arrays.asList(new Object[]{Prettify.PR_LITERAL, Pattern.compile("^(?:\\d+|\\d*\\.\\d+)(?:%|[a-z]+)?", Pattern.CASE_INSENSITIVE)}));
+        // A hex color
+        _fallthroughStylePatterns.add(Arrays.asList(new Object[]{Prettify.PR_LITERAL, Pattern.compile("^#(?:[0-9a-f]{3}){1,2}", Pattern.CASE_INSENSITIVE)}));
+        // An identifier
+        _fallthroughStylePatterns.add(Arrays.asList(new Object[]{Prettify.PR_PLAIN, Pattern.compile("^-?(?:[_a-z]|(?:\\\\[\\da-f]+ ?))(?:[_a-z\\d\\-]|\\\\(?:\\\\[\\da-f]+ ?))*", Pattern.CASE_INSENSITIVE)}));
+        // A run of punctuation
+        _fallthroughStylePatterns.add(Arrays.asList(new Object[]{Prettify.PR_PUNCTUATION, Pattern.compile("^[^\\s\\w\\'\\\"]+", Pattern.CASE_INSENSITIVE)}));
+
+        setShortcutStylePatterns(_shortcutStylePatterns);
+        setFallthroughStylePatterns(_fallthroughStylePatterns);
+
+        setExtendedLangs(Arrays.asList(new Lang[]{new LangCssKeyword(), new LangCssString()}));
+    }
+
+    public static List<String> getExtensions() {
+        return Arrays.asList(new String[]{"css"});
+    }
+
+    protected static class LangCssKeyword extends Lang {
+
+        public LangCssKeyword() {
+            List<List<Object>> _shortcutStylePatterns = new ArrayList<List<Object>>();
+            List<List<Object>> _fallthroughStylePatterns = new ArrayList<List<Object>>();
+
+            _fallthroughStylePatterns.add(Arrays.asList(new Object[]{Prettify.PR_KEYWORD, Pattern.compile("^-?(?:[_a-z]|(?:\\\\[\\da-f]+ ?))(?:[_a-z\\d\\-]|\\\\(?:\\\\[\\da-f]+ ?))*", Pattern.CASE_INSENSITIVE)}));
+
+            setShortcutStylePatterns(_shortcutStylePatterns);
+            setFallthroughStylePatterns(_fallthroughStylePatterns);
+        }
+
+        public static List<String> getExtensions() {
+            return Arrays.asList(new String[]{"css-kw"});
+        }
+    }
+
+    protected static class LangCssString extends Lang {
+
+        public LangCssString() {
+            List<List<Object>> _shortcutStylePatterns = new ArrayList<List<Object>>();
+            List<List<Object>> _fallthroughStylePatterns = new ArrayList<List<Object>>();
+
+            _fallthroughStylePatterns.add(Arrays.asList(new Object[]{Prettify.PR_STRING, Pattern.compile("^[^\\)\\\"\\']+")}));
+
+            setShortcutStylePatterns(_shortcutStylePatterns);
+            setFallthroughStylePatterns(_fallthroughStylePatterns);
+        }
+
+        public static List<String> getExtensions() {
+            return Arrays.asList(new String[]{"css-str"});
+        }
+    }
+}
diff --git a/src/prettify/lang/LangGo.java b/src/prettify/lang/LangGo.java
new file mode 100644
index 0000000..05bfa2f
--- /dev/null
+++ b/src/prettify/lang/LangGo.java
@@ -0,0 +1,15 @@
+package prettify.lang;
+
+import java.util.Arrays;
+import java.util.List;
+import prettify.Lang;
+
+public class LangGo extends Lang {
+
+    public LangGo() {
+    }
+
+    public static List<String> getExtensions() {
+        return Arrays.asList(new String[]{});
+    }
+}
diff --git a/src/prettify/lang/LangProto.java b/src/prettify/lang/LangProto.java
new file mode 100644
index 0000000..8941ec8
--- /dev/null
+++ b/src/prettify/lang/LangProto.java
@@ -0,0 +1,15 @@
+package prettify.lang;
+
+import java.util.Arrays;
+import java.util.List;
+import prettify.Lang;
+
+public class LangProto extends Lang {
+
+    public LangProto() {
+    }
+
+    public static List<String> getExtensions() {
+        return Arrays.asList(new String[]{});
+    }
+}
diff --git a/src/prettify/lang/LangScala.java b/src/prettify/lang/LangScala.java
new file mode 100644
index 0000000..6622694
--- /dev/null
+++ b/src/prettify/lang/LangScala.java
@@ -0,0 +1,15 @@
+package prettify.lang;
+
+import java.util.Arrays;
+import java.util.List;
+import prettify.Lang;
+
+public class LangScala extends Lang {
+
+    public LangScala() {
+    }
+
+    public static List<String> getExtensions() {
+        return Arrays.asList(new String[]{});
+    }
+}
diff --git a/src/prettify/lang/LangSql.java b/src/prettify/lang/LangSql.java
new file mode 100644
index 0000000..59f06af
--- /dev/null
+++ b/src/prettify/lang/LangSql.java
@@ -0,0 +1,15 @@
+package prettify.lang;
+
+import java.util.Arrays;
+import java.util.List;
+import prettify.Lang;
+
+public class LangSql extends Lang {
+
+    public LangSql() {
+    }
+
+    public static List<String> getExtensions() {
+        return Arrays.asList(new String[]{});
+    }
+}
diff --git a/src/prettify/lang/LangVb.java b/src/prettify/lang/LangVb.java
new file mode 100644
index 0000000..8e39256
--- /dev/null
+++ b/src/prettify/lang/LangVb.java
@@ -0,0 +1,70 @@
+// Copyright (C) 2009 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package prettify.lang;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Pattern;
+import prettify.Lang;
+import prettify.Prettify;
+
+/**
+ * @fileoverview
+ * Registers a language handler for various flavors of basic.
+ *
+ *
+ * To use, include prettify.js and this file in your HTML page.
+ * Then put your code in an HTML tag like
+ *      <pre class="prettyprint lang-vb"></pre>
+ *
+ *
+ * http://msdn.microsoft.com/en-us/library/aa711638(VS.71).aspx defines the
+ * visual basic grammar lexical grammar.
+ *
+ * @author mikesamuel@gmail.com
+ */
+public class LangVb extends Lang {
+
+    public LangVb() {
+        List<List<Object>> _shortcutStylePatterns = new ArrayList<List<Object>>();
+        List<List<Object>> _fallthroughStylePatterns = new ArrayList<List<Object>>();
+
+        // Whitespace
+        _shortcutStylePatterns.add(Arrays.asList(new Object[]{Prettify.PR_COMMENT, Pattern.compile("^[\\t\\n\\r \\xA0\\u2028\\u2029]+"), null, "\t\n\r " + Character.toString((char) 0xA0) + "\u2028\u2029"}));
+        // A double quoted string with quotes escaped by doubling them.
+        // A single character can be suffixed with C.
+        _shortcutStylePatterns.add(Arrays.asList(new Object[]{Prettify.PR_STRING, Pattern.compile("^(?:[\\\"\\u201C\\u201D](?:[^\\\"\\u201C\\u201D]|[\\\"\\u201C\\u201D]{2})(?:[\\\"\\u201C\\u201D]c|$)|[\\\"\\u201C\\u201D](?:[^\\\"\\u201C\\u201D]|[\\\"\\u201C\\u201D]{2})*(?:[\\\"\\u201C\\u201D]|$))", Pattern.CASE_INSENSITIVE), null, "\"\u201C\u201D"}));
+        // A comment starts with a single quote and runs until the end of the line.
+        _shortcutStylePatterns.add(Arrays.asList(new Object[]{Prettify.PR_COMMENT, Pattern.compile("^[\\'\\u2018\\u2019][^\\r\\n\\u2028\\u2029]*"), null, "'\u2018\u2019"}));
+        _fallthroughStylePatterns.add(Arrays.asList(new Object[]{Prettify.PR_KEYWORD, Pattern.compile("^(?:AddHandler|AddressOf|Alias|And|AndAlso|Ansi|As|Assembly|Auto|Boolean|ByRef|Byte|ByVal|Call|Case|Catch|CBool|CByte|CChar|CDate|CDbl|CDec|Char|CInt|Class|CLng|CObj|Const|CShort|CSng|CStr|CType|Date|Decimal|Declare|Default|Delegate|Dim|DirectCast|Do|Double|Each|Else|ElseIf|End|EndIf|Enum|Erase|Error|Event|Exit|Finally|For|Friend|Function|Get|GetType|GoSub|GoTo|Handles|If|Implements|Imports|In|Inherits|Integer|Interface|Is|Let|Lib|Like|Long|Loop|Me|Mod|Module|MustInherit|MustOverride|MyBase|MyClass|Namespace|New|Next|Not|NotInheritable|NotOverridable|Object|On|Option|Optional|Or|OrElse|Overloads|Overridable|Overrides|ParamArray|Preserve|Private|Property|Protected|Public|RaiseEvent|ReadOnly|ReDim|RemoveHandler|Resume|Return|Select|Set|Shadows|Shared|Short|Single|Static|Step|Stop|String|Structure|Sub|SyncLock|Then|Throw|To|Try|TypeOf|Unicode|Until|Variant|Wend|When|While|With|WithEvents|WriteOnly|Xor|EndIf|GoSub|Let|Variant|Wend)\\b", Pattern.CASE_INSENSITIVE), null}));
+        // A second comment form
+        _fallthroughStylePatterns.add(Arrays.asList(new Object[]{Prettify.PR_COMMENT, Pattern.compile("^REM[^\\r\\n\\u2028\\u2029]*", Pattern.CASE_INSENSITIVE)}));
+        // A boolean, numeric, or date literal.
+        _fallthroughStylePatterns.add(Arrays.asList(new Object[]{Prettify.PR_LITERAL, Pattern.compile("^(?:True\\b|False\\b|Nothing\\b|\\d+(?:E[+\\-]?\\d+[FRD]?|[FRDSIL])?|(?:&H[0-9A-F]+|&O[0-7]+)[SIL]?|\\d*\\.\\d+(?:E[+\\-]?\\d+)?[FRD]?|#\\s+(?:\\d+[\\-\\/]\\d+[\\-\\/]\\d+(?:\\s+\\d+:\\d+(?::\\d+)?(\\s*(?:AM|PM))?)?|\\d+:\\d+(?::\\d+)?(\\s*(?:AM|PM))?)\\s+#)", Pattern.CASE_INSENSITIVE)}));
+        // An identifier?
+        _fallthroughStylePatterns.add(Arrays.asList(new Object[]{Prettify.PR_PLAIN, Pattern.compile("^(?:(?:[a-z]|_\\w)\\w*|\\[(?:[a-z]|_\\w)\\w*\\])", Pattern.CASE_INSENSITIVE)}));
+        // A run of punctuation
+        _fallthroughStylePatterns.add(Arrays.asList(new Object[]{Prettify.PR_PUNCTUATION, Pattern.compile("^[^\\w\\t\\n\\r \\\"\\'\\[\\]\\xA0\\u2018\\u2019\\u201C\\u201D\\u2028\\u2029]+")}));
+        // Square brackets
+        _fallthroughStylePatterns.add(Arrays.asList(new Object[]{Prettify.PR_PUNCTUATION, Pattern.compile("^(?:\\[|\\])")}));
+
+        setShortcutStylePatterns(_shortcutStylePatterns);
+        setFallthroughStylePatterns(_fallthroughStylePatterns);
+    }
+
+    public static List<String> getExtensions() {
+        return Arrays.asList(new String[]{"vb", "vbs"});
+    }
+}
diff --git a/src/prettify/lang/LangWiki.java b/src/prettify/lang/LangWiki.java
new file mode 100644
index 0000000..d878351
--- /dev/null
+++ b/src/prettify/lang/LangWiki.java
@@ -0,0 +1,15 @@
+package prettify.lang;
+
+import java.util.Arrays;
+import java.util.List;
+import prettify.Lang;
+
+public class LangWiki extends Lang {
+
+    public LangWiki() {
+    }
+
+    public static List<String> getExtensions() {
+        return Arrays.asList(new String[]{});
+    }
+}
diff --git a/src/prettify/lang/LangYaml.java b/src/prettify/lang/LangYaml.java
new file mode 100644
index 0000000..7e78937
--- /dev/null
+++ b/src/prettify/lang/LangYaml.java
@@ -0,0 +1,15 @@
+package prettify.lang;
+
+import java.util.Arrays;
+import java.util.List;
+import prettify.Lang;
+
+public class LangYaml extends Lang {
+
+    public LangYaml() {
+    }
+
+    public static List<String> getExtensions() {
+        return Arrays.asList(new String[]{});
+    }
+}
diff --git a/src/prettify/theme/ThemeDefault.java b/src/prettify/theme/ThemeDefault.java
new file mode 100644
index 0000000..43cdceb
--- /dev/null
+++ b/src/prettify/theme/ThemeDefault.java
@@ -0,0 +1,89 @@
+package prettify.theme;
+
+import java.awt.Color;
+import java.awt.Font;
+import prettify.Theme;
+
+/**
+ * Default theme.
+ */
+public class ThemeDefault extends Theme {
+
+    public ThemeDefault() {
+        super();
+
+        setFont(new Font("Consolas", Font.PLAIN, 12));
+        setBackground(Color.white);
+
+        setHighlightedBackground(Color.decode("0xeee"));
+
+        setGutterText(Color.decode("0x000"));
+        setGutterBorderColor(Color.decode("0xeee"));
+        setGutterBorderWidth(3);
+        setGutterTextFont(new Font("Verdana", Font.PLAIN, 11));
+        setGutterTextPaddingLeft(7);
+        setGutterTextPaddingRight(7);
+
+        Style plainStyle = new Style();
+        plainStyle.setColor(Color.decode("0x000"));
+        setPlain(plainStyle);
+
+        Style style;
+
+        style = new Style();
+        style.setColor(Color.decode("0x080"));
+        setString(style);
+
+        style = new Style();
+        style.setColor(Color.decode("0x008"));
+        setKeyword(style);
+
+        style = new Style();
+        style.setColor(Color.decode("0x800"));
+        setComment(style);
+
+        style = new Style();
+        style.setColor(Color.decode("0x606"));
+        setType(style);
+
+        style = new Style();
+        style.setColor(Color.decode("0x066"));
+        setLiteral(style);
+
+        style = new Style();
+        style.setColor(Color.decode("0x660"));
+        setPunctuation(style);
+
+        style = new Style();
+        style.setColor(Color.decode("0x008"));
+        setTag(style);
+
+        setDeclaration(plainStyle);
+
+        style = new Style();
+        style.setColor(Color.decode("0x606"));
+        setAttributeName(style);
+
+        style = new Style();
+        style.setColor(Color.decode("0x080"));
+        setAttributeValue(style);
+
+        setNoCode(plainStyle);
+
+        style = new Style();
+        style.setColor(Color.decode("0x660"));
+        setOpenBracket(style);
+
+        style = new Style();
+        style.setColor(Color.decode("0x660"));
+        setCloseBracket(style);
+
+        style = new Style();
+        style.setColor(Color.decode("0x606"));
+        setVariable(style);
+
+        style = new Style();
+        style.setColor(Color.red);
+        setFunction(style);
+    }
+}
diff --git a/src/prettify/theme/ThemeDesert.java b/src/prettify/theme/ThemeDesert.java
new file mode 100644
index 0000000..70398d0
--- /dev/null
+++ b/src/prettify/theme/ThemeDesert.java
@@ -0,0 +1,91 @@
+package prettify.theme;
+
+import java.awt.Color;
+import java.awt.Font;
+import prettify.Theme;
+
+/**
+ * Desert theme.
+ */
+public class ThemeDesert extends Theme {
+
+    public ThemeDesert() {
+        super();
+
+        /* desert scheme ported from vim to google prettify */
+
+        setFont(new Font("Consolas", Font.PLAIN, 12));
+        setBackground(Color.decode("0x000"));
+
+        setHighlightedBackground(Color.decode("0x111"));
+
+        setGutterText(Color.decode("0xfff"));
+        setGutterBorderColor(Color.decode("0x111"));
+        setGutterBorderWidth(3);
+        setGutterTextFont(new Font("Verdana", Font.PLAIN, 11));
+        setGutterTextPaddingLeft(7);
+        setGutterTextPaddingRight(7);
+
+        Style plainStyle = new Style();
+        plainStyle.setColor(Color.decode("0xfff"));
+        setPlain(plainStyle);
+
+        Style style;
+
+        style = new Style();
+        style.setColor(Color.decode("0xffa0a0")); /* string  - pink */
+        setString(style);
+
+        style = new Style();
+        style.setColor(Color.decode("0xf0e68c"));
+        style.setBold(true);
+        setKeyword(style);
+
+        style = new Style();
+        style.setColor(Color.decode("0x87ceeb")); /* comment - skyblue */
+        setComment(style);
+
+        style = new Style();
+        style.setColor(Color.decode("0x98fb98")); /* type    - lightgreen */
+        setType(style);
+
+        style = new Style();
+        style.setColor(Color.decode("0xcd5c5c")); /* literal - darkred */
+        setLiteral(style);
+
+        style = new Style();
+        style.setColor(Color.decode("0xfff"));
+        setPunctuation(style);
+
+        style = new Style();
+        style.setColor(Color.decode("0xf0e68c"));/* html/xml tag    - lightyellow */
+        style.setBold(true);
+        setTag(style);
+
+        style = new Style();
+        style.setColor(Color.decode("0x98fb98")); /* decimal         - lightgreen */
+        setDeclaration(style);
+
+        style = new Style();
+        style.setColor(Color.decode("0xbdb76b")); /* attribute name  - khaki */
+        style.setBold(true);
+        setAttributeName(style);
+
+        style = new Style();
+        style.setColor(Color.decode("0xffa0a0")); /* attribute value - pink */
+        style.setBold(true);
+        setAttributeValue(style);
+
+        style = new Style();
+        style.setColor(Color.decode("0x333"));
+        setNoCode(style);
+
+        setOpenBracket(plainStyle);
+
+        setCloseBracket(plainStyle);
+
+        setVariable(plainStyle);
+
+        setFunction(plainStyle);
+    }
+}
diff --git a/src/prettify/theme/ThemeSonsOfObsidian.java b/src/prettify/theme/ThemeSonsOfObsidian.java
new file mode 100644
index 0000000..08920bb
--- /dev/null
+++ b/src/prettify/theme/ThemeSonsOfObsidian.java
@@ -0,0 +1,91 @@
+package prettify.theme;
+
+import java.awt.Color;
+import java.awt.Font;
+import prettify.Theme;
+
+/**
+ * Son of Obsidian theme.
+ * @author Alex Ford
+ */
+public class ThemeSonsOfObsidian extends Theme {
+
+    public ThemeSonsOfObsidian() {
+        super();
+
+        /*
+         * Derived from einaros's Sons of Obsidian theme at
+         * http://studiostyl.es/schemes/son-of-obsidian by
+         * Alex Ford of CodeTunnel:
+         * http://CodeTunnel.com/blog/post/71/google-code-prettify-obsidian-theme
+         */
+
+        setFont(new Font("Consolas", Font.PLAIN, 12));
+        setBackground(Color.white);
+
+        setHighlightedBackground(Color.decode("0x111"));
+
+        setGutterText(Color.decode("0xF1F2F3"));
+        setGutterBorderColor(Color.decode("0x111"));
+        setGutterBorderWidth(3);
+        setGutterTextFont(new Font("Verdana", Font.PLAIN, 11));
+        setGutterTextPaddingLeft(7);
+        setGutterTextPaddingRight(7);
+
+        Style plainStyle = new Style();
+        plainStyle.setColor(Color.decode("0xF1F2F3"));
+        setPlain(plainStyle);
+
+        Style style;
+
+        style = new Style();
+        style.setColor(Color.decode("0xEC7600"));
+        setString(style);
+
+        style = new Style();
+        style.setColor(Color.decode("0x93C763"));
+        setKeyword(style);
+
+        style = new Style();
+        style.setColor(Color.decode("0x66747B"));
+        setComment(style);
+
+        style = new Style();
+        style.setColor(Color.decode("0x678CB1"));
+        setType(style);
+
+        style = new Style();
+        style.setColor(Color.decode("0xFACD22"));
+        setLiteral(style);
+
+        style = new Style();
+        style.setColor(Color.decode("0xF1F2F3"));
+        setPunctuation(style);
+
+        style = new Style();
+        style.setColor(Color.decode("0x8AC763"));
+        setTag(style);
+
+        style = new Style();
+        style.setColor(Color.decode("0x800080"));
+        setDeclaration(style);
+
+        style = new Style();
+        style.setColor(Color.decode("0xE0E2E4"));
+        setAttributeName(style);
+
+        style = new Style();
+        style.setColor(Color.decode("0xEC7600"));
+        setAttributeValue(style);
+
+        setNoCode(plainStyle);
+
+        setOpenBracket(plainStyle);
+
+        setCloseBracket(plainStyle);
+
+        setVariable(plainStyle);
+
+        setFunction(plainStyle);
+    }
+}
diff --git a/src/prettify/theme/ThemeSunburst.java b/src/prettify/theme/ThemeSunburst.java
new file mode 100644
index 0000000..ef75c80
--- /dev/null
+++ b/src/prettify/theme/ThemeSunburst.java
@@ -0,0 +1,88 @@
+package prettify.theme;
+
+import java.awt.Color;
+import java.awt.Font;
+import prettify.Theme;
+
+/**
+ * Sunbrust theme.
+ * Vim sunburst theme by David Leibovic.
+ * @author David Leibovic
+ */
+public class ThemeSunburst extends Theme {
+
+    public ThemeSunburst() {
+        super();
+
+        /* Vim sunburst theme by David Leibovic */
+
+        setFont(new Font("Consolas", Font.PLAIN, 12));
+        setBackground(Color.black);
+
+        setHighlightedBackground(Color.decode("0xAEAEAE"));
+
+        setGutterText(Color.decode("0xfff"));
+        setGutterBorderColor(Color.decode("0xAEAEAE"));
+        setGutterBorderWidth(3);
+        setGutterTextFont(new Font("Verdana", Font.PLAIN, 11));
+        setGutterTextPaddingLeft(7);
+        setGutterTextPaddingRight(7);
+
+        Style plainStyle = new Style();
+        plainStyle.setColor(Color.decode("0xfff"));
+        setPlain(plainStyle);
+
+        Style style;
+
+        style = new Style();
+        style.setColor(Color.decode("0x65B042")); /* string  - green */
+        setString(style);
+
+        style = new Style();
+        style.setColor(Color.decode("0xE28964")); /* keyword - dark pink */
+        setKeyword(style);
+
+        style = new Style();
+        style.setColor(Color.decode("0xAEAEAE")); /* comment - gray */
+        style.setItalic(true);
+        setComment(style);
+
+        style = new Style();
+        style.setColor(Color.decode("0x89bdff")); /* type - light blue */
+        setType(style);
+
+        style = new Style();
+        style.setColor(Color.decode("0x3387CC")); /* literal - blue */
+        setLiteral(style);
+
+        style = new Style();
+        style.setColor(Color.decode("0xfff")); /* punctuation - white */
+        setPunctuation(style);
+
+        style = new Style();
+        style.setColor(Color.decode("0x89bdff")); /* html/xml tag    - light blue */
+        setTag(style);
+
+        style = new Style();
+        style.setColor(Color.decode("0x3387CC")); /* decimal - blue */
+        setDeclaration(style);
+
+        style = new Style();
+        style.setColor(Color.decode("0xbdb76b")); /* html/xml attribute name  - khaki */
+        setAttributeName(style);
+
+        style = new Style();
+        style.setColor(Color.decode("0x65B042")); /* html/xml attribute value - green */
+        setAttributeValue(style);
+
+        setNoCode(plainStyle);
+
+        setOpenBracket(plainStyle);
+
+        setCloseBracket(plainStyle);
+
+        setVariable(plainStyle);
+
+        setFunction(plainStyle);
+    }
+}
diff --git a/test/prettify/PrettifyTest.java b/test/prettify/PrettifyTest.java
index 8d4cf42..89c9c52 100644
--- a/test/prettify/PrettifyTest.java
+++ b/test/prettify/PrettifyTest.java
@@ -13,8 +13,6 @@
 // limitations under the License.
 package prettify;
 
-import java.util.Map;
-import java.util.TreeMap;
 import java.util.ListIterator;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -70,116 +68,78 @@
         List<Object> decorations, compare;
 
         source = new String(readFile(new File(packagePath + "source/bash.txt")));
-        job = new Job();
-        job.setBasePos(0);
-        job.setSourceCode(source);
-        prettify.langHandlerForExtension(null, source).decorate(job);
+        prettify.langHandlerForExtension(null, source).decorate(job = new Job(0, source));
         decorations = job.getDecorations();
         compare = readResult(new String(readFile(new File(packagePath + "result/bash.txt"))));
         assertArrayEquals("bash", decorations.toArray(), compare.toArray());
 
         source = new String(readFile(new File(packagePath + "source/bash_lang.txt")));
-        job = new Job();
-        job.setBasePos(0);
-        job.setSourceCode(source);
-        prettify.langHandlerForExtension("sh", source).decorate(job);
+        prettify.langHandlerForExtension("sh", source).decorate(job = new Job(0, source));
         decorations = removeNewLine(job.getDecorations(), source);
         compare = readResult(new String(readFile(new File(packagePath + "result/bash_lang.txt"))), true);
         assertArrayEquals("bash_lang", decorations.toArray(), compare.toArray());
 
         source = new String(readFile(new File(packagePath + "source/java.txt")));
-        job = new Job();
-        job.setBasePos(0);
-        job.setSourceCode(source);
-        prettify.langHandlerForExtension(null, source).decorate(job);
+        prettify.langHandlerForExtension(null, source).decorate(job = new Job(0, source));
         decorations = job.getDecorations();
         compare = readResult(new String(readFile(new File(packagePath + "result/java.txt"))));
         assertArrayEquals("java", decorations.toArray(), compare.toArray());
 
-
         source = new String(readFile(new File(packagePath + "source/java_lang.txt")));
-        job = new Job();
-        job.setBasePos(0);
-        job.setSourceCode(source);
-        prettify.langHandlerForExtension("java", source).decorate(job);
+        prettify.langHandlerForExtension("java", source).decorate(job = new Job(0, source));
         decorations = removeNewLine(job.getDecorations(), source);
         compare = readResult(new String(readFile(new File(packagePath + "result/java_lang.txt"))), true);
         assertArrayEquals("java_lang", decorations.toArray(), compare.toArray());
 
         source = new String(readFile(new File(packagePath + "source/C.txt")));
-        job = new Job();
-        job.setBasePos(0);
-        job.setSourceCode(source);
-        prettify.langHandlerForExtension(null, source).decorate(job);
+        prettify.langHandlerForExtension(null, source).decorate(job = new Job(0, source));
         decorations = job.getDecorations();
         compare = readResult(new String(readFile(new File(packagePath + "result/C.txt"))));
         assertArrayEquals("C", decorations.toArray(), compare.toArray());
 
-
         source = new String(readFile(new File(packagePath + "source/C_lang.txt")));
-        job = new Job();
-        job.setBasePos(0);
-        job.setSourceCode(source);
-        prettify.langHandlerForExtension("c", source).decorate(job);
+        prettify.langHandlerForExtension("c", source).decorate(job = new Job(0, source));
         decorations = removeNewLine(job.getDecorations(), source);
         compare = readResult(new String(readFile(new File(packagePath + "result/C_lang.txt"))), true);
         assertArrayEquals("C_lang", decorations.toArray(), compare.toArray());
 
         source = new String(readFile(new File(packagePath + "source/Cpp.txt")));
-        job = new Job();
-        job.setBasePos(0);
-        job.setSourceCode(source);
-        prettify.langHandlerForExtension(null, source).decorate(job);
+        prettify.langHandlerForExtension(null, source).decorate(job = new Job(0, source));
         decorations = job.getDecorations();
         compare = readResult(new String(readFile(new File(packagePath + "result/Cpp.txt"))));
         assertArrayEquals("Cpp", decorations.toArray(), compare.toArray());
 
         source = new String(readFile(new File(packagePath + "source/Cpp_lang.txt")));
-        job = new Job();
-        job.setBasePos(0);
-        job.setSourceCode(source);
-        prettify.langHandlerForExtension("cpp", source).decorate(job);
+        prettify.langHandlerForExtension("cpp", source).decorate(job = new Job(0, source));
         decorations = removeNewLine(job.getDecorations(), source);
         compare = readResult(new String(readFile(new File(packagePath + "result/Cpp_lang.txt"))), true);
         assertArrayEquals("Cpp_lang", decorations.toArray(), compare.toArray());
 
         source = new String(readFile(new File(packagePath + "source/javascript.txt")));
-        job = new Job();
-        job.setBasePos(0);
-        job.setSourceCode(source);
-        prettify.langHandlerForExtension(null, source).decorate(job);
+        prettify.langHandlerForExtension(null, source).decorate(job = new Job(0, source));
         decorations = job.getDecorations();
         compare = readResult(new String(readFile(new File(packagePath + "result/javascript.txt"))));
         assertArrayEquals("javascript", decorations.toArray(), compare.toArray());
 
         source = new String(readFile(new File(packagePath + "source/perl.txt")));
-        job = new Job();
-        job.setBasePos(0);
-        job.setSourceCode(source);
-        prettify.langHandlerForExtension(null, source).decorate(job);
+        prettify.langHandlerForExtension(null, source).decorate(job = new Job(0, source));
         decorations = job.getDecorations();
         compare = readResult(new String(readFile(new File(packagePath + "result/perl.txt"))));
         assertArrayEquals("perl", decorations.toArray(), compare.toArray());
 
-
         source = new String(readFile(new File(packagePath + "source/python.txt")));
-        job = new Job();
-        job.setBasePos(0);
-        job.setSourceCode(source);
-        prettify.langHandlerForExtension(null, source).decorate(job);
+        prettify.langHandlerForExtension(null, source).decorate(job = new Job(0, source));
         decorations = job.getDecorations();
         compare = readResult(new String(readFile(new File(packagePath + "result/python.txt"))));
         assertArrayEquals("python", decorations.toArray(), compare.toArray());
 
         source = new String(readFile(new File(packagePath + "source/python_lang.txt")));
-        job = new Job();
-        job.setBasePos(0);
-        job.setSourceCode(source);
-        prettify.langHandlerForExtension("py", source).decorate(job);
+        prettify.langHandlerForExtension("py", source).decorate(job = new Job(0, source));
         decorations = removeNewLine(job.getDecorations(), source);
         compare = readResult(new String(readFile(new File(packagePath + "result/python_lang.txt"))), true);
         assertArrayEquals("python_lang", decorations.toArray(), compare.toArray());
 
+        // porting in progress
     }
 
     /**
@@ -241,25 +201,7 @@
         }
 //        matcher.appendTail(sb);
 
-        // use TreeMap to remove entrys with same pos
-        Map<Integer, Object> orderedMap = new TreeMap<Integer, Object>();
-        for (int i = 0, iEnd = decorations.size(); i < iEnd; i++) {
-            orderedMap.put((Integer) decorations.get(i), decorations.get(i + 1));
-            i++;
-        }
-        // remove adjacent style
-        List<Object> returnList = new ArrayList<Object>(orderedMap.size() + 1);
-        String previousStyle = null;
-        for (Integer _pos : orderedMap.keySet()) {
-            if (previousStyle != null && previousStyle.equals(orderedMap.get(_pos))) {
-                continue;
-            }
-            returnList.add(_pos);
-            returnList.add(orderedMap.get(_pos));
-            previousStyle = (String) orderedMap.get(_pos);
-        }
-
-        return returnList;
+        return Util.removeDuplicates(decorations);
     }
 
     public static List<Object> readResult(String result) {
@@ -291,41 +233,25 @@
             }
         }
 
-        StringBuffer sb = new StringBuffer();
+        StringBuffer sb = new StringBuffer(); // for debug
         Pattern pattern = Pattern.compile("`([A-Z]{3})([^`]*?)`END", Pattern.MULTILINE | Pattern.DOTALL);
         Matcher matcher = pattern.matcher(result);
         while (matcher.find()) {
-            matcher.appendReplacement(sb, "");
-            sb.append(matcher.group(2));
+            matcher.appendReplacement(sb, ""); // for debug
+            sb.append(matcher.group(2)); // for debug
 
             returnList.add(count);
             returnList.add(matcher.group(1).toLowerCase());
             count += matcher.group(2).length();
         }
         matcher.appendTail(sb);
-        String plainCode = sb.toString();
+        String plainCode = sb.toString(); // for debug
 
         if (removeNewLine) {
-            // use TreeMap to remove entrys with same pos
-            Map<Integer, Object> orderedMap = new TreeMap<Integer, Object>();
-            for (int i = 0, iEnd = returnList.size(); i < iEnd; i++) {
-                orderedMap.put((Integer) returnList.get(i), returnList.get(i + 1));
-                i++;
-            }
-            // remove adjacent style
-            List<Object> _returnList = new ArrayList<Object>(orderedMap.size() + 1);
-            String previousStyle = null;
-            for (Integer _pos : orderedMap.keySet()) {
-                if (previousStyle != null && previousStyle.equals(orderedMap.get(_pos))) {
-                    continue;
-                }
-                _returnList.add(_pos);
-                _returnList.add(orderedMap.get(_pos));
-                previousStyle = (String) orderedMap.get(_pos);
-            }
-            returnList = _returnList;
+            returnList = Util.removeDuplicates(returnList);
         }
 
+        // for debug
 //        System.out.println(plainCode);
         for (int i = 0, iEnd = returnList.size(); i < iEnd; i++) {
             int end = i + 2 < iEnd ? (Integer) returnList.get(i + 2) : plainCode.length();