There are several ways of doing this. The collision detection itself you can do without a physics engine – you just need a vertex list for the letter forms. Do you need additional physics, or just a bouncing ball that reacts to letter shapes?
If you don’t need additional physics, then you can create the effect you want by combining these two things:

This geomerative example: Tutorial_07_HelloWorld_getPoints.pde

polygoncircle collision detection. http://jeffreythompson.org/collisiondetection/polycircle.php
Your list of letter points is the polygon. Run it through the polygoncircle collision algorithm to check whether the circle is colliding. Then bounce the ball (or whatever action you choose).
Here is a simple demo with a mouse.
/**
* LetterCollisions
* 20200524 Jeremy Douglass  Processing 3.5.4
* https://discourse.processing.org/t/usingfisicaandgeomerativetoaddphysicstotypography/21225/2
*
* Move the mouse over letters to test collision detection.
*
* Combines:
* 1. This geomerative example: Tutorial_07_HelloWorld_getPoints.pde
* 2. polygoncircle collision detection. http://jeffreythompson.org/collisiondetection/polycircle.php
*/
import geomerative.*;
// Declare the objects we are going to use, so that they are accesible from setup() and from draw()
RFont f;
RShape grp;
RPoint[] points;
void setup() {
// Initilaize the sketch
size(600, 400);
frameRate(24);
// Choice of colors
background(255);
fill(255, 102, 0);
stroke(0);
// VERY IMPORTANT: Allways initialize the library in the setup
RG.init(this);
// Load the font file we want to use (the file must be in the data folder in the sketch floder), with the size 60 and the alignment CENTER
grp = RG.getText("Hello world!", "FreeSans.ttf", 90, CENTER);
// Enable smoothing
smooth();
}
void draw() {
// Clean frame
background(255);
// Set the origin to draw in the middle of the sketch
int xoff = width/2;
int yoff = 3*height/5;
translate(xoff, yoff);
// express mouse in new coordinates
int mx = mouseXxoff;
int my = mouseYyoff;
boolean hit = false;
// Draw the group of shapes
noFill();
stroke(0, 0, 200, 150);
RG.setPolygonizer(RG.ADAPTATIVE);
grp.draw();
// Get the points on the curve's shape
//RG.setPolygonizer(RG.UNIFORMSTEP);
//RG.setPolygonizerStep(map(float(mouseY), 0.0, float(height), 0.0, 1.0));
RG.setPolygonizer(RG.UNIFORMLENGTH);
RG.setPolygonizerLength(8); // set gap in px between points around letter
// Loop over individual letters in group
// and process their points separately
for (int c=0; c<grp.children.length; c++) {
points = grp.children[c].getPoints();
// If there are any points
if (points != null) {
// draw the letter outline
fill(0, 200, 0, 32);
stroke(0, 200, 0);
beginShape();
for (int i=0; i<points.length; i++) {
vertex(points[i].x, points[i].y);
}
endShape();
// draw box on each vertex
stroke(0, 128, 0);
for (int i=0; i<points.length; i++) {
rect(points[i].x1, points[i].y1, 3, 3);
}
// check for mouse collision with this letter
// record a hit if mouse is touching this letter
// and use it for drawing later
if(polyCircle(points, mx, my, 10)) hit=true;
}
}
// draw the mouse is hit(red) or not(blue)
if(hit) fill(255,0,0,64);
else fill(0,0,255,64);
ellipse(mx, my, 20, 20);
}
// PolyCircle collision functions adapted from:
// http://jeffreythompson.org/collisiondetection/polycircle.php
// 1. replace "PVector" with "RPPoint"
// 2. remove "closest point" circle display
// POLYGON/CIRCLE
boolean polyCircle(RPoint[] vertices, float cx, float cy, float r) {
// go through each of the vertices, plus
// the next vertex in the list
int next = 0;
for (int current=0; current<vertices.length; current++) {
// get next vertex in list
// if we've hit the end, wrap around to 0
next = current+1;
if (next == vertices.length) next = 0;
// get the RPoints at our current position
// this makes our if statement a little cleaner
RPoint vc = vertices[current]; // c for "current"
RPoint vn = vertices[next]; // n for "next"
// check for collision between the circle and
// a line formed between the two vertices
boolean collision = lineCircle(vc.x,vc.y, vn.x,vn.y, cx,cy,r);
if (collision) return true;
}
// the above algorithm only checks if the circle
// is touching the edges of the polygon – in most
// cases this is enough, but you can uncomment the
// following code to also test if the center of the
// circle is inside the polygon
// boolean centerInside = polygonPoint(vertices, cx,cy);
// if (centerInside) return true;
// otherwise, after all that, return false
return false;
}
// LINE/CIRCLE
boolean lineCircle(float x1, float y1, float x2, float y2, float cx, float cy, float r) {
// is either end INSIDE the circle?
// if so, return true immediately
boolean inside1 = pointCircle(x1,y1, cx,cy,r);
boolean inside2 = pointCircle(x2,y2, cx,cy,r);
if (inside1  inside2) return true;
// get length of the line
float distX = x1  x2;
float distY = y1  y2;
float len = sqrt( (distX*distX) + (distY*distY) );
// get dot product of the line and circle
float dot = ( ((cxx1)*(x2x1)) + ((cyy1)*(y2y1)) ) / pow(len,2);
// find the closest point on the line
float closestX = x1 + (dot * (x2x1));
float closestY = y1 + (dot * (y2y1));
// is this point actually on the line segment?
// if so keep going, but if not, return false
boolean onSegment = linePoint(x1,y1,x2,y2, closestX,closestY);
if (!onSegment) return false;
// optionally, draw a circle at the closest point
// on the line
//////fill(255,0,0);
//////noStroke();
//////ellipse(closestX, closestY, 20, 20);
// get distance to closest point
distX = closestX  cx;
distY = closestY  cy;
float distance = sqrt( (distX*distX) + (distY*distY) );
// is the circle on the line?
if (distance <= r) {
return true;
}
return false;
}
// LINE/POINT
boolean linePoint(float x1, float y1, float x2, float y2, float px, float py) {
// get distance from the point to the two ends of the line
float d1 = dist(px,py, x1,y1);
float d2 = dist(px,py, x2,y2);
// get the length of the line
float lineLen = dist(x1,y1, x2,y2);
// since floats are so minutely accurate, add
// a little buffer zone that will give collision
float buffer = 0.1; // higher # = less accurate
// if the two distances are equal to the line's
// length, the point is on the line!
// note we use the buffer here to give a range, rather
// than one #
if (d1+d2 >= lineLenbuffer && d1+d2 <= lineLen+buffer) {
return true;
}
return false;
}
// POINT/CIRCLE
boolean pointCircle(float px, float py, float cx, float cy, float r) {
// get distance between the point and circle's center
// using the Pythagorean Theorem
float distX = px  cx;
float distY = py  cy;
float distance = sqrt( (distX*distX) + (distY*distY) );
// if the distance is less than the circle's
// radius the point is inside!
if (distance <= r) {
return true;
}
return false;
}
// POLYGON/POINT
// only needed if you're going to check if the circle
// is INSIDE the polygon
boolean polygonPoint(RPoint[] vertices, float px, float py) {
boolean collision = false;
// go through each of the vertices, plus the next
// vertex in the list
int next = 0;
for (int current=0; current<vertices.length; current++) {
// get next vertex in list
// if we've hit the end, wrap around to 0
next = current+1;
if (next == vertices.length) next = 0;
// get the RPoints at our current position
// this makes our if statement a little cleaner
RPoint vc = vertices[current]; // c for "current"
RPoint vn = vertices[next]; // n for "next"
// compare position, flip 'collision' variable
// back and forth
if (((vc.y > py && vn.y < py)  (vc.y < py && vn.y > py)) &&
(px < (vn.xvc.x)*(pyvc.y) / (vn.yvc.y)+vc.x)) {
collision = !collision;
}
}
return collision;
}