Requesting a specific font pixel height

Hi,

I want to create a font that uses a specific pixel height, so I do something like this:

fontmap = pango_cairo_font_map_new();
fontdesc = pango_font_description_from_string("Liberation 144px");
context = pango_font_map_create_context(fontmap);
font = pango_font_map_load_font(fontmap, context, fontdesc);
m = pango_font_get_metrics(font, NULL);
        
height = pango_font_metrics_get_height(m) / PANGO_SCALE;

Surprisingly, “height” is now 167 even though I requested a font height of 144 pixels. Is there any way to create a font whose pixel height is exactly or at least closely what I requested? Currently, there seems to be a rather significant deviation from the requested pixel size and the actual pixel height, e.g. I’m getting a font whose height is 167 pixels when requesting a 144 pixel font.

Thanks for any help!

The value returned by pango_font_metrics_get_height() is in Pango units, which is 1024th of a point (not pixel).

Thanks for your reply, but I know that the return value is in Pango units and the code I have posted in the OP takes care of it, see:

height = pango_font_metrics_get_height(m) / PANGO_SCALE;

So unfortunately, your answer doesn’t solve my problem :frowning:

I have seen a very similar question in the past, I think it was unanswered. So I wonder if it is a Pango bug, or our wrong understanding of the API docs. Note that pango has ways to get bounding boxes for text, you may use that to adjust the size. For my current app, I do not care much about text size currently, but I may do later. (https://github.com/StefanSalewski/SDT)

According to the docs this gives you the line height, not the font height:

So is there a way to create a font that has a specific line height in pixels because that’s actually what I’d need. I want to create a font that has a specific line height in pixels. Is there any way to do that?

From Pango.FontMetrics there is pango_font_metrics_get_ascent and pango_font_metrics_get_descent. So with some math we should be able to adjust our
pango_font_description_from_string() to get the desired size.

I don’t understand why I’d need pango_font_metrics_get_ascent and pango_font_metrics_get_descent for that because I can get the line height from pango_font_metrics_get_height and the line height is what I’m after. But the only solution I can think of is some sort of trial and error, constantly re-creating fonts in different sizes until I get one that matches exactly (or very closely) my desired line height but that’s of course a very ugly solution.

Is that what you had in mind when you talked about “with some math we should be able to […] get the desired size” or am I missing something here? Please elaborate.

I have very little knowledge of Pango and I don’t know what “font size” corresponds to. But you could find out by printing out all metrics. Let’s assume “font size” is ascent + descent. Then I would assume that line height is a certain percentage (> 100) of the size. If you find out that percentage for a particular font, you can calculate the the font size for a desired line height. All unverified though.

Maybe you can get better help if you explain your use case. Maybe PangoLayout is what you are looking for.

Pango is indeed not easy. Initially I had some trouble to get the PangoFontMetrics object at all, which is required for Pango.FontMetrics. Luckily your initial example shows us already how we can get it. My next problem is to exactly understand what you desire: From your initial post, you just desire that your returned height variable is exactly 144? Well it is easy to fix, in my code below the fix is

  # now fix so that height is exactly 144
  let fixFactor = FontHeight / height
  desc.setAbsoluteSize(FontHeight.float * pango.SCALE.float * fixFactor)

No trial and error needed.

But I would assume that your real intent is to position a text exactly into an existing rectangle? So we have additionally care for offsets?

As I had some trouble to remember pango stuff, I started with A python example of how to create png images with text through pangocairo · GitHub. That is my test1 proc in the code below. Then I created test2 proc, which used your initial code to create the PangoFontMetrics. With the fixed size, the second echo output prints exactly 144 as requested. But indeed all that is a bit difficult. I guess with Blend2d it will be not easy too, but at least Blend2d is still an active project, so maybe I should switch soon to Blend2D?

#https://gist.github.com/dov/ae4b889acb3659ca58fba1dba103ad49

import gintro/[cairo, pango, pangocairo]

const
  Width = 256
  Height=256
  Font = "Serif" # font name

proc test1 =
  let surface = cairo.imageSurfaceCreate(cairo.Format.argb32, Width, Height)
  let cr = cairo.newContext(surface)
  cr.rectangle(0,0,Width,Height)
  cr.setSource(0, 0, 0.5)
  cr.fill
  let layout = pangocairo.createLayout(cr)
  let desc = pango.newFontDescription(Font)
  desc.set_family("Serif")
  desc.set_size(48 * pango.SCALE)
  layout.set_font_description(desc)
  layout.setMarkup("A <b>bold</b>\nidea", "A <b>bold</b>\nidea".len)
  layout.setAlignment(pango.Alignment.center)
  var incRectangle, logRectangle: pango.Rectangle
  layout.getExtents(incRectangle, logRectangle)

  var (textWidth, textHeight) = (logRectangle.width / pango.SCALE, logRectangle.height / pango.SCALE)
  cr.moveto(Width / 2 - textWidth / 2, Height / 2 - textHeight / 2)
  cr.setSource(1,1,1)
  pangocairo.showLayout(cr,layout)
  # cr.fill # not needed
  let status = surface.writeToPng("example1.png")
  
proc test2 =
  const FontHeight = 144
  const Width = 1024
  let surface = cairo.imageSurfaceCreate(cairo.Format.argb32, Width, Height)
  let cr = cairo.newContext(surface)

  let layout = pangocairo.createLayout(cr)
  let desc = pango.newFontDescription(Font)
  desc.setFamily("Serif")
  desc.setAbsoluteSize(FontHeight.float * pango.SCALE.float)
  layout.set_font_description(desc)
  let fontmap = pangocairo.newFontMap()
  let pangoContext = createContext(fontmap)
  var font = fontMap.loadFont(pangoContext, desc)
  var m = font.getMetrics()
        
  var height = m.getHeight / pango.SCALE;
  echo height
  
  # now fix so that height is exactly 144
  let fixFactor = FontHeight / height
  desc.setAbsoluteSize(FontHeight.float * pango.SCALE.float * fixFactor)
  layout.set_font_description(desc)
  font = fontMap.loadFont(pangoContext, desc)
  m = font.getMetrics()
        
  height = m.getHeight / pango.SCALE;
  echo height # now this is 144!
  
  layout.setText("Your Font")
  layout.setAlignment(pango.Alignment.center)
  var incRectangle, logRectangle: pango.Rectangle
  layout.getExtents(incRectangle, logRectangle)
  var (textWidth, textHeight) = (logRectangle.width / pango.SCALE, logRectangle.height / pango.SCALE)
  cr.moveto(Width / 2 - textWidth / 2, Height / 2 - textHeight / 2)
  cr.rectangle(0,0,Width,incRectangle.height / pango.SCALE)
  cr.setSource(0, 0, 0.5)
  cr.fill
  
  cr.setSource(1,1,1)
  pangocairo.showLayout(cr,layout)
  #cr.fill
  let status = surface.writeToPng("example2.png")

test1()
test2()

salewski@hx90 ~/pango $ ./font 
168.0
144.0

[EDIT]

Finally I found the example with rotated text again: PangoCairo – 1.0: Rendering with Cairo

After spending some time again with Pango, I still have no idea what you really intend? Is is just some form of homework from a strange old teacher?

I think, for pixel based layout, we have to use PangoLayout (Pango.Layout) because Pango is mostly Point based (1 point is 1/72 inch). Mr Clasen told you already that for example pango_font_metrics_get_height() uses point, not pixel as unit. PangoLayout functions like Pango.Layout.get_pixel_extents gives us sizes in pixels, which we can use to position text. I think my own apps like https://github.com/StefanSalewski/salewski-chess or https://github.com/StefanSalewski/SDT do it that way, but I can not remember details.

Yes, the point (!) of my earlier reply was points-vs-pixels, not PANGO_SCALE.

1 Like

Yes, the point (!) of my earlier reply was points-vs-pixels, not PANGO_SCALE

But that still doesn’t make sense to me. As you can see from the code in my first post, I’m requesting a 144px font but pango_font_metrics_get_height(m) returns a height of 167 for the font. Now if 167 really were a value in points (as you say), how can it ever be more than the pixel height? AFAIK points are converted to pixels like this:

pixels = points * (dpi / 72);

So on a 96dpi display, a 167pt font would be something like 222px and a 144px font can never be 167pt. So even if pango_font_metrics_get_height returns a value in points, it doesn’t make sense to me because a 144px font can’t be the same as a 167pt font. Or am I misunderstanding something here?