Vertical and horizontal center glyphs

Hi guys,

I’m a noob and I’ve made a sketch where by clicking you can shuffle through an alphabet’s glyphs and they get vertically and horizontally centered according to their size and the size of the sketch. I was stuck for a bit but I stumbled along untill I found some logic that works.

The sketch has an array that stores the different glyphs and I shuffle through them. Since I wanted to vertically center the glyphs and type glyphs have different heights, I figured I would need some code that could determine the height of each glyph. So I searched online and used some of the last code shared in this thread: https://forum.processing.org/two/discussion/19869/is-it-really-not-possible-to-determine-the-precise-height-of-a-text

With the height and width data being determined for each different glyph I then “position their midpoints” on the sketch’s midpoints by doing some simple math. Arriving at this solution took me a while. I worked casually on this over the weekend as an exercise.

Now I’m curious if there are better and simpler ways to making this work because along the way I got lost/confused and was missing some important info about the nuts and bolts of how the type glyphs and Processing work.

Could anyone share any other approaches?

Thanks

int i = 0; //change value to start at a different letter

char [] alphabet = {
  'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 
  'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
}; 

void setup(){
 size(600,600);
}

void draw(){
  
  int xPos = width/2;   //horizontal position where you want the glyphs to be centered 
  int yPos = height/2;  //vertical position where you want the glyphs to be centered
  
  int textSize = 400;
  PFont typeface;
  typeface = createFont("Arial",textSize);
  PFont.Glyph glyph = typeface.getGlyph(alphabet[i]);
  int t = glyph.topExtent;
  int h = glyph.height;
  int l = glyph.leftExtent;
  int w = glyph.width;
  int yMove = height/2-((yPos-t)+h/2);
  int xMove = width/2-((xPos+l)+w/2);
  
  background(255);
  fill(0);
  
  textFont(typeface);
  textSize(textSize);
  textLeading(0);
  text(alphabet[i], width/2+xMove, height/2+yMove);
  
  
  ////////////////// lines for showing top limit and bottom limit of glyph
  stroke(255,0,0);
  line(0,(yPos-t)+yMove,width,(yPos-t)+yMove); //red line top of glyph 
  strokeWeight(5);
  point((xPos+l)+xMove, (yPos-t)+yMove);
  strokeWeight(1);
  stroke(0,255,0);
  line(0,((yPos-t)+h+yMove),width,((yPos-t)+h)+yMove); //green line height of glyph
  
  //light blue line middle of glyph (helped during coding) comment out blue line (below) to see this one
  stroke(0,255,255);
  line(0,(((yPos-t)+h/2))+yMove,width,((yPos-t)+h/2)+yMove); 

  
  //////////////// line and point for showing center of "canvas"
  strokeWeight(1);
  stroke(0,0,255);
  line(0,yPos,width,yPos); //blue line at vertical midpoint of canvas
  strokeWeight(5);
  point(width/2, height/2);
  strokeWeight(1);


  //////////////// glyph bounding box
  noFill();
  stroke(0,255,0);
  rect((xPos+l)+xMove, (yPos-t)+yMove, w, h);
  
  
   println(alphabet[i]+" "+"t:"+t+" "+" "+"h:"+h+" "+" "+"mouseY:"+mouseY);
   println("yMove:"+yMove);
}

void mousePressed() {
      
  ////////////////// shuffle through glyphs in array
  if(mouseX > width/2){
    i = i+1;
  } else {
    i = i-1;
  }
  
  //////////////// ifs for controlling end behaviors when stepping through array
  if(i == 26){
   i = 0; 
  }
  
  if(i < 0){
   i = 25; 
  }
   
}
1 Like

very cool program to demonstrate typography glyphs. A different way to code this would be to create a function for each glyph. For example, you have strokeWeight() being used multiple times with 1 parameter. Hope this answers your question. Good luck on your project! Hope to see 2.0 :smiley:

Interesting – thank you for sharing!

Yes, this definitely aligns the glyph differently than if you centered it using textMode(CENTER,CENTER).

Your code has width/2 and height/2 (and many other terms) cancelling out everywhere. You should factor that out. For example:

  1. yPos = height/2 … so:
  2. yMove = height/2 - yPos + t + h/2 is actually…
    yMove = height/2 - height/2 + t + h/2
    yMove = t + h/2
  3. Now look at this line:
    line(0,((yPos-t)+h+yMove),width,((yPos-t)+h)+yMove);
    [made an error when quickly unpacking, but I think you see the point – t cancels, et cetera]

Followup – I tried wrapping your approaches to glyph bounding boxes up in a class.

/**
 * GlyphBoxes
 * 2018-06 Processing 3.3.6 
 */

PFont.Glyph glyph;
GlyphBox glyphbox;

int i = 0; // letter index
char [] alphabet = {
  'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 
  'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
};

void setup() {
  size(600, 600);
  textLeading(0);
  textFont(createFont("Arial", 400), 400);
  glyphbox = new GlyphBox("Arial", 400, alphabet[i]);
  glyphbox.align("CENTER"); // CENTER / BASELINE / NONE
}

void draw() {
  background(255);
  stroke(0);

  // screen crosshairs
  line(0, height/2.0, width, height/2.0);
  line(width/2.0, 0, width/2.0, height);

  // center the screen coordinates
  translate(width/2, height/2);

  // draw letter
  fill(0);
  glyphbox.text(0, 0);

  // draw some glyph lines
  noFill();
  stroke(0, 0, 255);
  glyphbox.boxbaseline();
  stroke(255, 0, 0);
  glyphbox.box();
  stroke(0, 255, 0);
  glyphbox.reticule();
}


class GlyphBox {
  String fontName;
  float textSize;
  PFont typeface;
  PFont.Glyph glyph;
  char character;
  String alignment = "";
  float x = 0; // x offset based on extent
  float y = 0; // y offset based on extent

  GlyphBox (String fontName, float textSize, char c) {
    this.fontName = fontName;
    this.textSize = textSize;
    this.typeface = createFont(fontName, textSize);
    this.character = c;
    this.glyph = typeface.getGlyph(c);
    this.alignUpdate();
  }
  
  void align(String s) {
    alignment = s;
    alignUpdate();
  }
  
  void alignUpdate() {
    if (alignment.equals("CENTER")) {
      // adjust center by glyph width/height
      x = -glyph.width/2.0;
      y = -glyph.height/2.0;
      // adjust center by glyph extent
      x -= glyph.leftExtent;
      y += glyph.topExtent;
    } else if (alignment.equals("BASELINE")) {
      y = 0;
      // adjust center by glyph width/height
      x = -glyph.width/2.0;
      // adjust center by glyph extent
      x -= glyph.leftExtent;
    } else if (alignment.equals("NONE")) {
      x = 0;
      y = 0;
    } else {
      x = 0;
      y = 0;
    }
  }
  
  void setChar(char c) {
    character = c;
    glyph = typeface.getGlyph(c);
    alignUpdate();
  }
  
  void text() {
    this.text(0, 0);
  }
  
  void text(float tx, float ty) {
    pushMatrix();
    translate(x, y);
    translate(0, 0);
    pushStyle();
    textFont(typeface);
    textSize(400);
    g.text(character, tx, ty);
    popStyle();
    popMatrix();
  }
  
  void box() {
    rect(x + glyph.leftExtent, y + -glyph.topExtent, glyph.width, glyph.height);
  }
  
  void boxbaseline() {
    topline();
    baseline();
    leftbaseline();
    rightbaseline();
  }
  
  void reticule() {
    midline();
    centerline();
  }
  
  void reticulebaseline() {
    midline();
    centerbaseline();
  }
  
  void topline() {
    line(x + glyph.leftExtent, y + -glyph.topExtent, 
      x + glyph.leftExtent + glyph.width, y + -glyph.topExtent);
  }
  
  void midline() {
    line(x + glyph.leftExtent, y + -glyph.topExtent + glyph.height/2.0, 
      x + glyph.leftExtent + glyph.width, y + -glyph.topExtent + glyph.height/2.0);
  }
  
  void baseline() {
    line(x + glyph.leftExtent, y, 
      x + glyph.leftExtent + glyph.width, y);
  }
  
  void bottomline() {
    line(x + glyph.leftExtent, y + glyph.height-glyph.topExtent, 
      x + glyph.leftExtent + glyph.width, y + glyph.height-glyph.topExtent);
  }
  
  void leftbaseline() {
    line(x + glyph.leftExtent, y + -glyph.topExtent, 
      x + glyph.leftExtent, y);
  }
  
  void leftline() {
    line(x + glyph.leftExtent, y + -glyph.topExtent, 
      x + glyph.leftExtent, y + -glyph.topExtent + glyph.height);
  }
  
  void centerline() {
    line(x + glyph.leftExtent + glyph.width/2.0, y + -glyph.topExtent, 
      x + glyph.leftExtent + glyph.width/2.0, y + -glyph.topExtent + glyph.height);
  }
  
  void centerbaseline() {
    line(x + glyph.leftExtent + glyph.width/2.0, y + -glyph.topExtent, 
      x + glyph.leftExtent + glyph.width/2.0, y);
  }
  
  void rightbaseline() {
    line(x + glyph.leftExtent + glyph.width, y + -glyph.topExtent, 
      x + glyph.leftExtent + glyph.width, y);
  }
  
  void rightline() {
    line(x + glyph.leftExtent + glyph.width, y + -glyph.topExtent, 
      x + glyph.leftExtent + glyph.width, y + -glyph.topExtent + glyph.height);
  }
}

void mousePressed() {
  // shuffle through glyphs in array
  if (mouseX > width/2) {
    i = (i+1) % 26;
  } else {
    i = i-1;
    if (i < 0) {
      i = 25;
    }
  }
  glyphbox.setChar(alphabet[i]);
}

RE: textAlign – Note that I believe your approach (centering the glyph on its height) is not compatible with setting textAlign(CENTER, CENTER) in the main sketch. If you wanted to make the glyph boxes responsive to the global textAlign style you could actually use g.textAlign / textAlignY to detect the current mode – e.g. if (g.textAlignY==CENTER) – but I’m not actually sure how you would then alter GlyphBox.align to make assign correct offsets to a textAlign centered glyph so that the bounding box would line up.

1 Like