AngusJohnson/Image32

Incorrect interpretation of USE_MY_METRICS

Closed this issue · 5 comments

Image32/source/Img32.Text.pas

Lines 1885 to 1890 in 4e2ba14

if (flag and USE_MY_METRICS <> 0) then
begin
if Result <> nil then
AffineTransform(a,b,c,d,e,f, result);
end else
AffineTransform(a,b,c,d,e,f, componentPaths);

It is my understanding that the USE_MY_METRICS flag has nothing to do with the path transformation. As I read it, if a child glyph has the USE_MY_METRICS flag set it means that the parent glyph should use the metrics of the flagged child glyph. The metrics being the left side-bearing, right side-bearing, and advance width.
The Apple docs aren't very clear on this. The Microsoft docs are a bit better - but not much.

Hopefully fixed now.

Sorry to keep going about this but I can't see that you're doing anything at all with the hmtx struct.

Its journey starts in GetGlyphMetricsInternal where you get the metrics for the "root" glyph...

Image32/source/Img32.Text.pas

Lines 2001 to 2005 in 20a2ec6

function TFontReader.GetGlyphMetricsInternal(glyphIdx: integer;
out pathsEx: TPathsEx): TGlyphMetrics;
begin
if IsValidFontFormat and
GetGlyphHorzMetrics(glyphIdx, result.hmtx) then

...and pass it on to GetGlyphPaths:

pathsEx := GetGlyphPaths(glyphIdx, result.hmtx, result.glyf); //gets raw splines

In GetGlyphPaths you pass it on to GetCompositeGlyph:

result := GetCompositeGlyph(tbl_glyf, tbl_hmtx) else

And GetCompositeGlyph copies it to a local temporary and passes that recursively on to GetGlyphPaths, etc., etc.

Image32/source/Img32.Text.pas

Lines 1880 to 1881 in 20a2ec6

component_tbl_hmtx := tbl_hmtx;
componentPaths := GetGlyphPaths(glyphIndex, component_tbl_hmtx, component_tbl_glyf);

In GetCompositeGlyph, once GetGlyphPaths returns, you examine the composite flag and if USE_MY_METRICS is set you copy it back from the local:

Image32/source/Img32.Text.pas

Lines 1887 to 1888 in 20a2ec6

if (flag and USE_MY_METRICS <> 0) then
tbl_hmtx := component_tbl_hmtx; //(#24)

Now, in none of the above is the metrics actually updated. The structure remains unmodified from start to end.

The problem is that you're only getting the metrics of the root GlyphID. What you need to do is get the metrics of each component (potentially) all the way down the tree.

For example, move the call to GetGlyphHorzMetrics from GetGlyphMetricsInternal into GetGlyphPaths so you're getting the metrics for the glyph component and have GetCompositeGlyph update the caller's metrics if the USE_MY_METRICS flag is set. You're almost doing it and it's as if this was your meaning all along.

function TFontReader.GetCompositeGlyph(var tbl_glyf: TFontTable_Glyf;
  var tbl_hmtx: TFontTable_Hmtx): TPathsEx;
...
    component_tbl_hmtx := Default(TFontTable_Hmtx);
    componentPaths := GetGlyphPaths(glyphIndex, component_tbl_hmtx, component_tbl_glyf);
    ...
    if (flag and USE_MY_METRICS <> 0) then
      tbl_hmtx := component_tbl_hmtx;               //(#24)

Btw, I believe the vmtx values should be copied as well. Sorry :-)

If you're planning on implementing point-to-point positioning (ARGS_ARE_XY_VALUES flag not set) at some time it might be easier to just pass the hmtx and vmtx values as "phantom-points" since you will need those for that anyway.

Thanks again Anders for your valuable feedback.
It's been quite a while since I've closely looked at this stuff so I'm rather rusty about this now. I might look at this more closely soonish, but at the moment I'm occupied with other things, including improving the vectorization and color quantization code here.
Regarding implementing point-to-point positioning and hmtx and vmtx: if you can provide code that implements these, or links to where these are implemented elsewhere, I would of course fix this more quickly 😁🤞.

The problem is that you're only getting the metrics of the root GlyphID. What you need to do is get the metrics of each component (potentially) all the way down the tree.

I ended up taking a different approach which I like much better: Instead of passing the metric structure around, I simply implemented a GetGlyphMetric method that internally traverses the glyph tree to find the GlyphID which will supply the metric. Since it doesn't need to traverse the whole tree it's actually pretty efficient. It goes something like this:

function TRedacted.GetGlyphMetric(GlyphIndex: Word): TTrueTypeGlyphMetric;
var
  GlyphDataTable: TTrueTypeFontGlyphDataTable;

  procedure DoGetGlyphMetric(GlyphIndex: Word; var MetricIndex: Word);
  var
    Glyph: TCustomTrueTypeFontGlyphData;
    i: integer;
    CompositeGlyphData: TTrueTypeFontCompositeGlyphData;
    ComponentGlyphMetric: TTrueTypeGlyphMetric;
  begin
    Glyph := GlyphDataTable.GlyphData[GlyphIndex];

    // If glyph is a simple glyph then we will just use its index as the metric index.
    if (Glyph is TTrueTypeFontSimpleGlyphData) then
      // The default MetricIndex value has already been set by the caller.
      exit;

    // If glyph is a composite glyph, then we will either use its index or one of
    // its components index as the metric index.
    if Glyph is TTrueTypeFontCompositeGlyphData then
    begin
      // The default MetricIndex value has already been set by the caller.
      CompositeGlyphData := TTrueTypeFontCompositeGlyphData(Glyph);

      // Recursively process composite glyph components
      for i := 0 to CompositeGlyphData.GlyphCount-1 do
        if (CompositeGlyphData.Glyph[i].Flags and TRedactedCompositeGlyph.GLYF_USE_MY_METRICS <> 0) then
        begin
          // We will use the index of the component. Set MetricIndex and recurse.
          MetricIndex := CompositeGlyphData.Glyph[i].GlyphIndex;

          DoGetGlyphMetric(CompositeGlyphData.Glyph[i].GlyphIndex, MetricIndex);

          // In theory multiple components could set USE_MY_METRICS but we
          // ignore that as it doesn't make sense.
          exit;
        end;
    end;
  end;

var
  MetricIndex: Word;
  VerticalMetricsTable: TRedactedVerticalMetricsTable;
begin
  Result := Default(TTrueTypeGlyphMetric);
  GlyphDataTable := TTrueTypeFontGlyphDataTable(GetTableByTableName('glyf'));
  if (GlyphDataTable = nil) then
    exit;

  MetricIndex := GlyphIndex;
  DoGetGlyphMetric(GlyphIndex, MetricIndex);

  Result.HorizontalMetric := FHorizontalMetrics.HorizontalMetric[MetricIndex];

  VerticalMetricsTable := TRedactedVerticalMetricsTable(GetTableByTableType(TRedactedVerticalMetricsTable.GetTableType));
  if (VerticalMetricsTable <> nil) then
    Result.VerticalMetric := VerticalMetricsTable.VerticalMetric[MetricIndex];
end;

Regarding implementing point-to-point positioning and hmtx and vmtx

Point-to-point position is pretty easy and the documentation is clear on how it's done. Unfortunately, a key part of that feature is "phantom points" and the details of those seem to be completely undocumented. I know what they represent (the four "corners" of the glyph), but apart from that it's pretty much guesswork.
I have also been unable to find any glyphs that actually use this feature so I'm concentrating on more important stuff for now.