@Override
  public MathsImageReturn getImage(MathsImageParams params) {
    long start = System.currentTimeMillis();
    MathsImageReturn result = new MathsImageReturn();
    result.setOk(false);
    result.setImage(EMPTY);
    result.setError("");

    try {
      // Parse XML
      Document mathml = parseMathml(params, result, start);
      if (mathml == null) {
        return result;
      }
      return getImage(params, mathml, result, start);
    } catch (Throwable t) {
      result.setError("MathML unexpected error - " + t.getMessage());
      t.printStackTrace();
      return result;
    }
  }
 /**
  * Parses mathml from the input into a DOM document. Split out so that subclass can call.
  *
  * @param params Parameters including MathML
  * @param result Blank result object
  * @param start Request start time (milliseconds since epoch)
  * @return Document or null if failed (in which case should return result)
  */
 protected Document parseMathml(MathsImageParams params, MathsImageReturn result, long start)
     throws Exception {
   try {
     Document mathml = parseMathml(params.getMathml());
     if (SHOWPERFORMANCE) {
       System.err.println("Parse DOM: " + (System.currentTimeMillis() - start));
     }
     return mathml;
   } catch (SAXParseException e) {
     int line = e.getLineNumber(), col = e.getColumnNumber();
     result.setError("MathML parse error at " + line + ":" + col + " - " + e.getMessage());
     return null;
   }
 }
  /**
   * Does real work; separated out so it can be efficiently called from subclass.
   *
   * @param params Request parameters
   * @param doc MathML as DOM document
   * @param result Initialised result object with blank fields
   * @param start Start time of request (milliseconds since epoch)
   * @return Return result
   * @throws IOException Any error creating image file
   */
  protected MathsImageReturn getImage(
      MathsImageParams params, Document doc, MathsImageReturn result, long start)
      throws IOException {
    initContext();

    Color fg;
    try {
      fg = convertRgb(params.getRgb());
    } catch (IllegalArgumentException e) {
      result.setError(e.getMessage());
      return result;
    }

    if (SHOWPERFORMANCE) {
      System.err.println("Setup: " + (System.currentTimeMillis() - start));
    }

    // Parse XML to JEuclid document
    DocumentElement document;
    preprocessForJEuclid(doc);
    document = DOMBuilder.getInstance().createJeuclidDom(doc);
    if (SHOWPERFORMANCE) {
      System.err.println("Parse: " + (System.currentTimeMillis() - start));
    }

    // Set layout options
    LayoutContextImpl layout = new LayoutContextImpl(LayoutContextImpl.getDefaultLayoutContext());
    layout.setParameter(Parameter.ANTIALIAS, Boolean.TRUE);
    // This size is hardcoded to go well with our default text size
    // and be one of the sizes that doesn't look too horrible.
    layout.setParameter(Parameter.MATHSIZE, params.getSize() * 15f);
    layout.setParameter(Parameter.SCRIPTSIZEMULTIPLIER, 0.86667f);
    layout.setParameter(Parameter.MATHCOLOR, fg);

    // These fonts are included with the JEuclid build so ought to work
    layout.setParameter(
        Parameter.FONTS_SERIF, Arrays.asList(new String[] {"DejaVu Serif", "Quivira"}));
    layout.setParameter(Parameter.FONTS_SCRIPT, Arrays.asList(new String[] {"Allura"}));
    layout.setParameter(Parameter.FONTS_SANSSERIF, "DejaVu Sans");
    layout.setParameter(Parameter.FONTS_MONOSPACED, "DejaVu Sans Mono");

    if (SHOWPERFORMANCE) {
      System.err.println("Layout: " + (System.currentTimeMillis() - start));
    }

    // Layout equation
    JEuclidView view = new JEuclidView(document, layout, context);
    float ascent = view.getAscentHeight();
    float descent = view.getDescentHeight();
    float width = view.getWidth();
    if (SHOWPERFORMANCE) {
      System.err.println("View: " + (System.currentTimeMillis() - start));
    }

    // Create new image to hold it
    int pixelWidth = Math.max(1, (int) Math.ceil(width)),
        pixelHeight = Math.max(1, (int) Math.ceil(ascent + descent));

    BufferedImage image = new BufferedImage(pixelWidth, pixelHeight, BufferedImage.TYPE_INT_ARGB);
    if (SHOWPERFORMANCE) {
      System.err.println("Image: " + (System.currentTimeMillis() - start));
    }
    view.draw(image.createGraphics(), 0, ascent);
    if (SHOWPERFORMANCE) {
      System.err.println("Draw: " + (System.currentTimeMillis() - start));
    }
    ByteArrayOutputStream output = new ByteArrayOutputStream();
    ImageIO.write(image, "png", output);
    if (SHOWPERFORMANCE) {
      System.err.println("PNG: " + (System.currentTimeMillis() - start));
    }

    // Save results
    result.setImage(output.toByteArray());
    result.setBaseline(BigInteger.valueOf(image.getHeight() - (int) Math.round(ascent)));
    result.setOk(true);

    if (SHOWPERFORMANCE) {
      System.err.println("End: " + (System.currentTimeMillis() - start));
    }
    return result;
  }