Primitive Superscript Subscript Text rendering.

edited July 2014 in Share Your Work

Below is a very limited implementation for super-scripted and sub-scripted text. Feel free to use and/or improve it. Here is a screen shot:

supersubscript

Sketch:

SuperSubText txt;
void setup()
{
  size(550, 300);
  txt = new SuperSubText("Calibri-48.vlw", 32, 24, 24, color(50, 50, 250));
}
PFont font;
void draw()
{
  background(123);
  manualExperiment();
  actualTest();
  //txt.demo(100, 250);
  noLoop();
}
void manualExperiment()
{
  float w = 0;
  int x = 100;
  int y = 100;
  font = loadFont("Calibri-48.vlw");
  String str = "";
  textFont(font);
  // text
  textSize(32);
  text("-X", x, y);
  w = textWidth("-3");
  // superscript
  textSize(16);
  text("-2", x+w, y-15);
  //w += textWidth("-2");
  // subscript
  textSize(16);
  text("1", x+w, y+15/2);
  w += textWidth("1");
  textSize(32);
  text(".", x+w, y);
}
void actualTest()
{
  float w = 0;
  int x = 100;
  int y = 100;
  font = loadFont("Calibri-48.vlw");
  String str = "";
  textFont(font);
  str = "-X<sup>-2</sup><sub>1</sub>.";
  txt = new SuperSubText("Calibri-48.vlw", 32, 16, 16, color(50, 50, 250));
  txt.layout(str, 100, 150);
  str = "a<sup>2</sup> +  b<sup>2</sup> = c<sup>2</sup>";
  txt.layout(str,50,200);
  txt = new SuperSubText("Calibri-48.vlw", 24, 16, 16, color(50, 50, 250));
  str = "Projective general linear group PGL<sub>2</sub>(F<sub>q</sub>) for q > 3.";
  txt.layout(str,50,250);

}

SuperSubText Class

static final String testString = "Text<sub>1</sub><sup>2</sup>, text<sup>2</sup><sub>1</sub>, more <sub>1</sub><sup>2</sup>text.";
static final String testString2 = "X<sub>2<sup>3<sup>4</sup></sup>,5</sub> is interesting!"; // not currently supported
//
// <sub>x</sub> subscript
// <sup>n</sup> superscript
//
// This is a very limited, primitive implementation. Only single 
//  subscript/superscript level is supported.  I was intending to 
//  support nested scripts, but for the moment this meets my needs. 
//
//  Note: The rendered result is an improvement of html as an item 
//        with both subscript and superscript does not offset in the x 
//        direction.
//        However if the widths are not  equal, the wider needs to come 
//        second to manage the offsets.
//
//  7/16/2014 jas0501
//
class SuperSubText
{
  static final int TEXT_SUPERSCRIPT_END = -1;
  static final int TEXT_SUBSCRIPT_END = -2;
  static final int TEXT_SUPERSCRIPT_START = 1;
  static final int TEXT_SUBSCRIPT_START = 2;
  static final int TEXT_SCRIPT = 0;
  static final String TOKEN_SUB_START = "<sub>";
  static final String TOKEN_SUB_END = "</sub>";
  static final String TOKEN_SUP_START = "<sup>";
  static final String TOKEN_SUP_END = "</sup>";

  int superYOffset = 0;
  int subYOffset = 0;
  String fontName;
  int fontSize;
  int superFontSize;
  int subFontSize;
  PFont font;
  color fill;
  ArrayList<Chunk> chunks;


  SuperSubText(String fontName_, int fontSize_, int superFontSize_, int subFontSize_, color fill_)
  {
    fontSize = fontSize_;
    superFontSize = superFontSize_;
    subFontSize = subFontSize_;
    fontName = fontName_;
    font = loadFont(fontName);
    superYOffset = superFontSize_;
    subYOffset = subFontSize_;
    chunks = new ArrayList<Chunk>();
    fill = fill_;
  }


  class Chunk
  {
    String text;
    int mode;
    int depth;
    int fontSize;
    Chunk(String s, int mode_, int depth_, int fontSize_)
    {
      text = s;
      mode = mode_;
      fontSize = fontSize_;
    }
  }

  void clean()
  {
    for (int i = chunks.size ()-1; i >= 0; i--) 
    {
      chunks.remove(i);
    }
  }


  void render(int x, int y)
  {
    int thisX=x;
    int thisY=y;
    fill(fill);
    boolean doubleScript = false;
    int prevMode;
    int preXOffset = 0;

    textFont(font, fontSize);
    for (Chunk k : chunks)
    {
      float textWidth;
      if (k.mode == TEXT_SUPERSCRIPT_END)
      {
        thisY = y;
      } else if (k.mode == TEXT_SUBSCRIPT_END)
      {
        thisY = y;
      } else if (k.mode == TEXT_SUPERSCRIPT_START)
      {

        thisY = y - superYOffset;
      } else if (k.mode == TEXT_SUBSCRIPT_START)
      {
        thisY = y + subYOffset/2;
      } else
      {
        textFont(font, k.fontSize);
        text(k.text, thisX, thisY);
        if (k.text.length() >0)
        {

          thisX += textWidth(k.text);
          preXOffset = (int)textWidth(k.text);
        } else
        {
          //text("|", thisX, thisY-50);
          thisX -= preXOffset;
        }
        thisY = y;
        textFont(font, fontSize);
      }
    }
  }

  //
  // format string in the form
  //   text<sub>subtext</sub><sup>supertext</sup>
  //
  void layout(String txt, int x_, int y_)
  {
    parse(txt);
    //displayChunks();
    render(x_, y_);
    clean();
  }


  void parse(String text)
  {
    int i = 0;
    int usedPos = -1;
    int currFontSize = fontSize;
    for (i = 0; i<text.length (); i++)
    {
      if (text.charAt(i) == TOKEN_SUB_START.charAt(0)) // have a potential token
      {
        if (isToken(i, text, TOKEN_SUB_START))
        {
          Chunk k = new Chunk(text.substring(usedPos+1, i), TEXT_SCRIPT, 0, currFontSize); 
          chunks.add(k);
          k = new Chunk("", TEXT_SUBSCRIPT_START, 0, currFontSize);
          currFontSize = subFontSize;
          chunks.add(k);
          i += TOKEN_SUB_START.length() - 1;
          usedPos = i;
        } else if (isToken(i, text, TOKEN_SUP_START))
        {
          Chunk k = new Chunk(text.substring(usedPos+1, i), TEXT_SCRIPT, 0, currFontSize); 
          chunks.add(k);
          k = new Chunk("", TEXT_SUPERSCRIPT_START, 0, currFontSize);
          currFontSize = subFontSize;
          chunks.add(k);
          i += TOKEN_SUP_START.length() - 1;
          usedPos = i;
        } else if (isToken(i, text, TOKEN_SUB_END))
        {
          Chunk k = new Chunk(text.substring(usedPos+1, i), TEXT_SCRIPT, 0, currFontSize); 
          chunks.add(k);
          k = new Chunk("", TEXT_SUBSCRIPT_END, 0, currFontSize);
          currFontSize = fontSize;
          chunks.add(k);
          i += TOKEN_SUB_END.length() - 1;
          usedPos = i;
        } else if (isToken(i, text, TOKEN_SUP_END))
        {
          Chunk k = new Chunk(text.substring(usedPos+1, i), TEXT_SCRIPT, 0, currFontSize); 
          chunks.add(k);
          k = new Chunk("", TEXT_SUPERSCRIPT_END, 0, currFontSize);
          currFontSize = fontSize;
          chunks.add(k);
          i += TOKEN_SUP_END.length() - 1;
          usedPos = i;
        }
      }
    }
    Chunk k = new Chunk(text.substring(usedPos+1, i), TEXT_SCRIPT, 0, currFontSize); 
    chunks.add(k);
  }


  boolean isToken(int i, String text, String token)
  {
    for (int j = 0; j < token.length (); j++)
    {
      if (!(text.charAt(i+j) == (token.charAt(j))))
      {
        return false;
      }
    }
    return true;
  }


  void displayChunks()
  {
    for (int i= 0; i<chunks.size (); i++)
    {
      Chunk k = chunks.get(i);
      print(k.text+displayMode(k.mode)+"|");
    }
  }


  String displayMode(int m)
  {
    String ret = "";
    switch(m)
    {
    case TEXT_SUPERSCRIPT_END:
      ret = TOKEN_SUP_END;
      break;
    case TEXT_SUBSCRIPT_END:
      ret = TOKEN_SUB_END;
      break;
    case TEXT_SUPERSCRIPT_START:
      ret = TOKEN_SUP_START;
      break;
    case TEXT_SUBSCRIPT_START:
      ret = TOKEN_SUB_START;
      break;
    case TEXT_SCRIPT:
      ret = "";
      break;
    }
    return ret;
  }


  void demo(int x_, int y_)
  {
    layout(testString, x_, y_); 
    clean();
    layout(testString2, x_, y_+100);
  }
}

Comments

  • edited July 2014

    Interesting project! Good for math formula display! <:-P

    BtW, just wanted to inform you that a List structure already got a clear() method:
    http://docs.oracle.com/javase/8/docs/api/java/util/List.html#clear--

    No need to roll your own clean() method! O:-)

    Also, I guess it'd be better to pass an already loaded Font reference to a class rather asking it to load it in our place! :-B

  • "Good for math formula display!"
    Or chemistry! :)]

    About the font: it can be argued, but I concur totally if there will be several instances of the class.

  • edited July 2014

    This code is a work in progress...

    The SuperSubText class would be better to not have the fill as a parameter and have the layout() just use the current setting of fill(). Then txt.layout() behaves just like text();

    I'll update the code when I have a few extra minutes.

    Long lines are an issue:

    Adding a layoutWidth() method compliment textWidth() method. And would permit detection of wide rendering, where the layout is wider than desired. The text could then be split, and layout() called with each piece.

    Adding a split() function based on width would be practical and the parser would easily detect super/subscript boundaries where the split, avoiding orphaned sub/super-scripts.

    Slight bug:

    As written the layout of "X<sub>123</sub><sup>2</sup>" would be off. the second super/subscript determines the x offset. In this case textWidth("2"), not textWidth("123"). For those cases where text has both sub-scripting and super-scripting, the offset should be the greater length.

Sign In or Register to comment.