Getting a PShape from an emoji...?

Processing has this PFont method, “undocumented” (not in the main public reference), called .getShape(), that will give you a PShape of a glyph from a font object: PFont docs

But there is a catch… it will only take a char as an argument, and emojis are either String or char[] arrays… so I guess I’m out of luck, I suppose.

Any alternative suggestions?

Method Font::createGlyphVector() which method PFont::getShape() internally invokes is overloaded to accept other datatypes besides just char[], such as int[] and String:

So you can try out your luck and ask the devs to add more datatype options for getShape()'s calling signature:

3 Likes

Thank you @GoToLoop! As usual, you are the best! I’ll study my options now :smiley:

2 Likes

I’m interested in this topic, but I don’t know anything about Java. Can I have an example please (if possible)? Let’s say the letter “1”.

1 Like

And I mean in Prosessing, not in P5 or somthing else!! Thanks a lot.

1 Like

Hi @stema253 ,

Have you tried the geomerative library.

Cheers
— mnse

1 Like

I’m interested in this topic, but I don’t know anything about Java. Can I have an example please (if possible)? Let’s say the letter “1”.

The following source code should get the glyphVector coordinates for ‘1’:

/*
  This demo uses java code to display the character '1' as
  well as the font outline and vectors (x,y coordinates) used
  to create it.  The outline was initially created offscreen,
  so a translation was required to pull it down so that we can
  see it.  A similar technique was used to reposition the vector
  points (marked by small circles) using an AffineTransform.  The
  points were found using a PathIterator and temporarily held
  in an array.
*/

import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform;
import java.awt.geom.PathIterator;

double[] coords = new double[2];
int index = 0;

int _wndW = 300;
int _wndH = 500;

class CanvasForDisplay extends Canvas {
  public void paint(Graphics g) {
    Graphics2D g2D = (Graphics2D) g;
    Font font = new Font("Verdana", Font.BOLD, 106);
    FontRenderContext fontRenderContext = g2D.getFontRenderContext();
    GlyphVector glVector = font.createGlyphVector(fontRenderContext, "1");
    g2D.drawGlyphVector(glVector, 120, 360);
    Shape s = glVector.getOutline();
    g2D.translate(120,150); // Initially drawn above our window so move it down
    g2D.draw(s);
    println("Shape =",s);
    println(s.getBounds());
    AffineTransform translate = new AffineTransform();
    translate.setToTranslation(0, 100);
    PathIterator path = s.getPathIterator(translate, 0.5);
    println("pathIterator =",path);
    println("windingRule =",path.getWindingRule());
    while (!path.isDone()) {
      path.currentSegment(coords);
      println("pt[" + index + "] = " + (int)coords[0] + " : " + (int)coords[1]);
      g2D.drawOval((int)coords[0], (int)coords[1], 4, 4);
      path.next();
      index++;
    }
  }
}

void setup() {
  surface.setVisible(false);  // Don't show default Processing window.
  Frame frame = new Frame("FontRenderContextExample");
  frame.setBounds(100, 100, _wndW, _wndH);
  frame.add(new CanvasForDisplay());
  frame.setVisible(true);
  frame.addWindowListener(new WindowAdapter() {
    public void windowClosing(WindowEvent e) {
      System.exit(0);
    }
  }
  );
}

Output:

1 Like

Cheers @stema253, @mnse and @svan !

I have played with Geomerative before, it could be a solution as it is getting a string and not a char, but I wonder how it goes about it internally!

Processing “as is” can get the contours of the “simple” glyphs, like ‘1’ fine, the problem arises if they are represented by multiple chars in Java (like emojis).

I’ll have a look at these!

1 Like

Is the emoji that you’re trying to get the shape of contained in a font collection? If so, what is the font’s name and the character’s number?

Addendum:

Font setting:

Font font = new Font("Webdings", Font.BOLD, 106);

When I enter this line of code:

GlyphVector glVector = font.createGlyphVector(fontRenderContext, new String("0x1F577"));

I get this output:

Which is not what I see when use println():

println(new String(Character.toChars(0x1F577)));

Addendum2:

Ok, I see how this works. When you use newString(“012345”); it will get the coordinates for each character in succession. If you look at the screen shot of Webdings older version you can see where these characters came from. It starts at zero on the first bar character on line 2 (not sure what the numbers are for the first line).

Addendum3:

Here’s the output for:

GlyphVector glVector = font.createGlyphVector(fontRenderContext, new String("012345"));

Which matches up perfectly:

2 Likes

Ok, you are getting the points! It looks very promising! But webdings map to single chars…

My question was generic, because a student of mine was asking about using emojis, I think we could try OpenSansEmoji/OpenSansEmoji.ttf at master · MorbZ/OpenSansEmoji · GitHub

2 Likes

I need a character number. Here’s the output new String(“012345”) for OpenSansEmoji.ttf but I don’t think that’s what you had in mind:

1 Like

Maybe :globe_with_meridians: U+1F310 Globe with meridians?

Using this code here’s the output for U+1F310 which is equal to 127760:

  GlyphVector glVector = font.createGlyphVector(fontRenderContext,String.format("%c", 127760));

Addendum:
Another way of getting emoji string for Webdings is like this:

GlyphVector glVector = font.createGlyphVector(fontRenderContext,"!");

or alternatively

GlyphVector glVector = font.createGlyphVector(fontRenderContext,String.format("\uf05e"));

You can see how to get all the webdings characters here:
https://speakingpowerpoint.wordpress.com/wp-content/uploads/2011/10/symbol-fonts-full-page3.jpg

Basically every keyboard character is mapped to a webding.

N.B.
This technique also works for Wingdings, Wingdings 2, and Wingdings 3 as well as other fonts, such as OpenSansEmoji.ttf

This gives access to the characters further down than what is shown above:

GlyphVector glVector = font.createGlyphVector(fontRenderContext,String.format("%c", 255));

Summary:
The range is 33 - 255 with this technique and should get the coordinates for all the characters using the following format:

GlyphVector glVector = font.createGlyphVector(fontRenderContext,String.format("%c", numberInRange));

Values for all the characters are listed here:
https://www.alanwood.net/demos/webdings.html

2 Likes

“Segoe UI Emoji” font available to me on W10:

int count = 0x1F310;
Font font = new Font("Segoe UI Emoji", Font.BOLD, 120);
GlyphVector glVector = font.createGlyphVector(fontRenderContext, Character.toString(count));

Example using the “Segoe UI Emoji” here:
Use emoji in fonts in Processing - #10 by glv

:)

2 Likes

A while back I worked on a wrapper to handle some font glyph stuff in the CamZup library, since Processing’s methods made PShapes with errors. iirc, there was an issue with the AWT path iterator repeating the same point at the open and close of a shape, and Processing not handling this case.

Supplying 0 detail to the AWT path iterator allows you to get the Bezier control points to the curve, rather than samples. Having the choice may allow you–or your students–to decide where there’s an advantage to each.

Above the sample detail is relatively big, leading to a blockier result.

import camzup.pfriendly.*;
import camzup.core.*;

YupJ2 graphics;
CurveEntity2[] glyCrv;

boolean showHandles = true;

MaterialSolid matCrv = new MaterialSolid()
  .setFill(true)
  .setFill(0xff4f4f4f)
  .setStroke(true)
  .setStroke(0xfffff7d5)
  .setStrokeWeight(2.0);

PFont font;

void settings() {
  size(1067, 600, YupJ2.PATH_STR);
}

void setup() {
  graphics = (YupJ2)getGraphics();
  PImage txtr = createImage(512, 512, ARGB);
  ZImage.rgb(txtr);

  textMode(SHAPE);
  String str = Character.toString(0x1f310);
  font = createFont("Segoe UI Emoji", 72.0);
  // The flag to separate glyphs must be false for this to work,
  // as separation depends on chars.
  glyCrv = TextShape.glyphCurve(font, 1.0, 0.0, false, str);
  
  // font = createFont("Webdings", 72.0f);
  // glyCrv = TextShape.glyphCurve(font, 1.0, 0.0, new int[] { 4, 5, 8 } );
  
  int lenGlyphs = glyCrv.length;
  float scalar = 1.5 * Utils.min(graphics.width, graphics.height);
  for(int i = 0; i < lenGlyphs; ++i) {
    CurveEntity2 glyph = glyCrv[i];
    glyph.reframe();
    glyph.scaleTo(scalar);
  }
}

void draw() {
  graphics.background(0xff202020);
  
  int lenGlyphs = glyCrv.length;
  for(int i = 0; i < lenGlyphs; ++i) {
    CurveEntity2 glyph = glyCrv[i];
    graphics.shape(glyph, matCrv);
    if (showHandles) {
      graphics.handles(glyph);
    }
  }
}

void mouseWheel(MouseEvent e) {
  float mWheel = e.getCount();
  graphics.zoomBy(-mWheel * 0.05);
}

void keyReleased() {
  if (key == ' ') {
    showHandles = !showHandles;
  } else if (key == 's') {
    String result = graphics.toSvgString(glyCrv, matCrv);
    saveStrings("data/text.svg", new String[] { result });
    println("Saved to svg.");
  }
}

There’s also an option for SVG output:

The tough part is preserving the cut-out shapes. The example code above uses an AWT-based renderer. If the P2D-like renderer is used, all the holes are opaque. Same deal with converting from the curve objects that I use to PShapes.

2 Likes

Hi, @behreajj and @svan,

I can attest the method works fine, you have to do some checking for when a vertex repeats (then you know an external polygon has closed). So you have to check yourself when a vertex is the same as the initial vertex and this indicates a closed polygon. If you are drawing directly from the vertices, you can just repeat the vertex without using endShape(CLOSE), use endShape() only.

Then the problem is to know when to switch to beginCountour() / endContour() for the holes, or when it is a separate filled part of the glyph… One might perhaps use the PShape .contains() method for that decision, but I was not drawing directly so I used shapely to decide and apply the holes.

On py5 I’m doing this (and then using other strategies, with the shapely library) to make polygons with holes:

        ...
        glyph_pt_lists = [[]]
        c_shp = font.get_shape(c, 1)
        vs3 = [c_shp.get_vertex(i) for i in range(c_shp.get_vertex_count())]
        vs = set()
        for vx, vy, _ in vs3:  # discarding vz
            x = vx + x_offset
            y = vy + y_offset
            glyph_pt_lists[-1].append((x, y))
            if (x, y) not in vs:
                vs.add((x, y))
            else:
                glyph_pt_lists.append([])  # will leave a trailing empty list

The following technique with Java’s Shape gives clean output and there is no need to get all the vectors of an emoji and reconstruct it into a PShape.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.awt.Font;
import java.awt.Shape;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform;

final int _wndW = 550;
final int _wndH = 400;

class CanvasDisplay extends Canvas {
  void paint(Graphics g) {
    Graphics2D g2D = (Graphics2D)g;
    Font font = new Font("Webdings", Font.BOLD, 106);
    FontRenderContext fontRenderContext = g2D.getFontRenderContext();
   // int value = 0x1F310;
    int value = 45;
    GlyphVector glVector = font.createGlyphVector(fontRenderContext, Character.toString(value));
    g2D.drawGlyphVector(glVector, 140, 280); // Black fill by default
    Shape s = glVector.getOutline();
    g2D.translate(140,140); 
    Color myRed = new Color(0.9, 0.1, 0.1);
    g2D.setColor(myRed);
    g2D.fill(s);
    g2D.draw(s);
    g2D.translate(140,140);
    g2D.setColor(Color.BLUE);
    g2D.fill(s);
    g2D.draw(s);
  }
}

void setup() {
  surface.setVisible(false);
  JFrame frame = new JFrame("GlyphVector Outline Example");
  frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  frame.setBounds(100, 100, _wndW, _wndH);
  frame.setResizable(false);
  CanvasDisplay display = new CanvasDisplay();
  frame.add(display);
  frame.setVisible(true);
}

1 Like

this is very cool, but my student needed the geometry for using it with pymunk in a 2D physics simulation…

Hello @svan,

Can you please add some comments to this code?

It would certainly benefit users seeing this for the first time and may encourage them to explore further.

This may all look unfamiliar to users new to Processing.

:)

1 Like

Done. Thanks for the suggestion.

1 Like