POV-Ray : Newsgroups : povray.general : text paths? : Re: text paths? Server Time
29 Jul 2024 18:16:07 EDT (-0400)
  Re: text paths?  
From: Thomas A  Fine
Date: 19 Nov 2010 13:49:24
Message: <4ce6c6b4$1@news.povray.org>
Well, I dug into the TTF format, and wrote a script that can pull out
the data for any glyph (and possibly for any traditional ascii-encoded
character).

So now it's a simple matter to write another script that turns this
data into whatever particular structure I want.  I believe that the
curves used in TTF fonts are compatible with the curves used in
sphere_sweep.  If that's the case, the rest should be incredibly
straight-forward.

The script is a real mess.  It might be reliable for pulling glyphs
out by glyph number, but there's no guaranteed ordering so that may
be trial and error to find your glyph.  It can lookup based on ascii
(or roman or ISO-whatever you want to call it) characters, but only
if the first encoding table in the CMAP section of the font happens
to contain that kind of encoding.

I could say "I'll clean it up and post it when it's nicer" -  but we
all know how that goes.   So I'm just going to append it here, FWIW.

ttfgetchar TTF_fontfile character
ttfgetchar TTF_fontfile -g glyphnumber

	tom

#!/usr/bin/perl

@platformID = ("Unicode", "Macintosh", "<reserved>", "Microsoft");
$specificID[1] = ["Roman", "Japanese", "Traditional Chinese", "Korean", "Arabic",
"Hebrew", "Greek", "Russian", "RSymbol", "Devanagari", "Gurmukhi", "Gujarati",
"Oriya", "Bengali", "Tamil", "Telugu", "Kannada", "Malayalam", "Sinhalese", "Burmese",
"Khmer", "Thai", "Laotian", "Georgian", "Armenian", "Simplified Chinese", "Tibetan",
"Mongolian", "Geez", "Slavic", "Vietnamese", "Sindhi", "(Uninterpreted)",];
$specificID[0] = ["Default semantics", "Version 1.1 semantics", "ISO 10646 1993
semantics (deprecated)", "Unicode 2.0 or later semantics"];

#http://developer.apple.com/fonts/ttrefman/RM06/Chap6.html

#TTF format
#  font directory
#    offset subtable
#      uint32 type { 'true' | 'typ1' | ... }
#      uint16 numTables number of tables
#      uint16 searchRange (base 2 order of mag table size, e.g. 128-255 = 8)*16
#      uint16 entrySelector log2 (searchRange)*16
#      uint16 rangeShift (numTables*16-searchRange)
#    table directory
#      repeated instances of
#        uint32 tag { cmap | glyf | head | hhea | hmtx | loca | maxp | name | post |
... }
#        uint32 checkSum
#        uint32 offset from beginning of sfnt (file?)
#        uint32 length (bytes, unpadded length)
#
#  cmap table
#    cmap index
#      uint16 version 0
#      uint16 numSubtables
#    encoding subtable
#      repeated instances of
#        uint16 platformID
#        uint16 platformSpecificID
#        uint32 offset of the mapping table
#    mapping tables of various formats based on offsets above?
#
#  loca table  - where is each glyf in the glyf table?
#    The size is based on data in the "head" table?
#
#  glyf data
#
# table types
#cmap - character to glyph mapping
#glyf - glyph data
#head - font header
#hhea - horizontal header
#hmtx - horizontal metrics
#loca - index to location
#maxp - maximum profile
#name - naming
#post - postscript
#cvt  - control value
#fpgm - font program
#hdmx - horizontal device metrics
#kern - kerning
#OS/2 - OS/2
#prep - control value program

$fontfile=shift(@ARGV);

if ($#ARGV == 1 && $ARGV[0] eq "-g") {
  $glyph_num=shift(@ARGV);
} elsif ($#ARGV == 0) {
  $char=shift(@ARGV);
  $ascii=ord($char);
} else {
  print "usage: $0 font char\n";
  print "       $0 font -g glyphindex\n";
  exit(1);
}

#uncomment to show debugging info
#open(DEBUG,">&STDERR");

open (TTF,$fontfile);
  $fonttype=str32();
  if ($fonttype eq '') { $fonttype = "Windows"; }
  $numTables=uint16();
  $searchRange=uint16();
  $entreySelector=uint16();
  $rangeShift=uint16();
  print DEBUG "$fonttype $numTables\n";
  for ($i=0; $i<$numTables; ++$i) {
    $tag=str32();
    $checksum=uint32();
    $offset=uint32();
    $length=uint32();
    $tableOff{$tag}=$offset;
    $tableLen{$tag}=$length;
    print DEBUG "$tag $offset:$length\n";
  }

  print DEBUG "\nhead\n";
  seek(TTF,$tableOff{'head'},SEEK_SET);
  print DEBUG "offset $tableOff{'head'}, ", tell(TTF), "\n";
  $version=str32();
  if ($version != "") { print "Bad header version!?", $version,"\n"; }
  skip32(); skip32();
  $magicnumber=uint32();
  if ($magicnumber != 0x5F0F3CF5) { printf("Bad magic!? %x\n", $magicnumber); }
  $headflags=uint16();
  skip16();
  skip32(); $created=uint32();
  skip32(); $modified=uint32();
  $fxmin=int16();     # should be signed
  $fymin=int16();     # should be signed
  $fxmax=int16();     # should be signed
  $fymax=int16();     # should be signed
  print DEBUG "BB $fxmin,$fymin - $fxmax,$fymax\n";
  $macStyle=uint16();
  skip16(); skip16();
  $indexToLocFormat=uint16();   #zero for short (16 bits), 1 for long (32 bits)
  skip16();

  print DEBUG "\ncmap\n";
  seek(TTF,$tableOff{'cmap'},SEEK_SET);
  print DEBUG "offset $tableOff{'cmap'}, ", tell(TTF), "\n";
  $cmap_verision = uint16();
  $numCmaps = uint16();
  print DEBUG "$numCmaps cmaps\n";
  for ($i=0; $i<$numCmaps; ++$i) {
    $platID = uint16();
    $platSpecID = uint16();
    $cmapOffset[$i] = uint32();
    print DEBUG "CMAP ";
    if (defined($platformID[$platID])) {
      print DEBUG "$platformID[$platID] ";
    } else {
      print DEBUG "$platID ";
    }
    if (defined(${$specificID[$platID]}[$platSpecID])) {
      print DEBUG ${$specificID[$platID]}[$platSpeciID], ", ";
    } else {
      print DEBUG "$platID ";
    }
    print DEBUG "$cmapOffset[$i]\n";
  }
  #OVER-ASSUMING - first subtable will be format 0 cmap that we want
  $format=uint16();
  $cSubLen=uint16();
  $lang=uint16();
  if ($format == 0) {
    for ($i=0; $i<256; ++$i) {
      $ascii2glyph[$i] = uint8();
    }
    ++$asciiOK;
  } else {
    print DEBUG "These are not the droids we're looking for\n";
  }

  #This is the offset into the glyf table for each glyph.
  # (relative to the glyf table, not the font file)
  print DEBUG "\nloca\n";
  seek(TTF,$tableOff{'loca'},SEEK_SET);
  if ($indexToLocFormat) {
    for ($i=0; $i<$tableLen{'loca'}/4; ++$i) {
      $goff[$i]=uint32();
      #print DEBUG "  $i - $goff[$i]\n";
    }
  } else {
    for ($i=0; $i<$tableLen{'loca'}/2; ++$i) {
      #note the *2 - for short tables, offset is in words not bytes
      $goff[$i]=uint16()*2;
      #print DEBUG "  $i - $goff[$i]\n";
    }
  }

  if (defined($ascii)) {
    if (! $asciiOK) {
      print STDERR "Couldn't find correct glyph table!\n";
      exit(1);
    }
    $glyph_num = $ascii2glyph[$ascii];
  }

  print DEBUG "\nglyf #$glyph_num\n";
  seek(TTF,$tableOff{'glyf'}+$goff[$glyph_num],SEEK_SET);
  $numContours=int16();
  print DEBUG "Contours: $numContours\n";
  $xmin=int16();     # should be signed
  $ymin=int16();     # should be signed
  $xmax=int16();     # should be signed
  $ymax=int16();     # should be signed
  print DEBUG "BB $xmin,$ymin - $xmax,$ymax\n";
  $lastEnd = -1;
  for ($i=0; $i<$numContours; ++$i) {
    $endPtsOfContours[$i] = uint16();
    $contourLen[$i] = $endPtsOfContours[$i] - $lastEnd;
    $lastEnd=$endPtsOfContours[$i];
    print DEBUG "Contour $i: end is $endPtsOfContours[$i]\n";
  }
  $instLen = uint16();
  for ($i=0; $i<$instLen; ++$i) {
    $instrs[$i] = uint8();
  }
  for ($i=0,$n=0; $i<$numContours; ++$i) {
    for ($j=0; $j<$contourLen[$i]; ++$j,++$n) {
      if ($repeating) {
        $flags{$i,$j} = $repeatflag;
	print DEBUG "repeating $repeating\n";
	--$repeating;
      } else {
        $flags{$i,$j} = uint8();
	if ($flags{$i,$j} & 0x8) {
	  $repeating = uint8();
	  $repeatflag = $flags{$i,$j};
	}
      }
      print DEBUG sprintf("flags($i,$j) = %08b\n",$flags{$i,$j});
    }
  }
  for ($i=0,$n=0; $i<$numContours; ++$i) {
    for ($j=0; $j<$contourLen[$i]; ++$j,++$n) {
      if (($flags{$i,$j} & 0x10) && (($flags{$i,$j} & 0x02)==0)) {
        $x{$i,$j} = $lastx;
      } elsif ($flags{$i,$j} & 0x02) {
        $tmpx = uint8();
	if (($flags{$i,$j} & 0x10)==0) { $tmpx = 0 - $tmpx; }
	$x{$i,$j} = $lastx + $tmpx;
      } else {
        $tmpx = int16();
        $x{$i,$j} = $lastx + $tmpx;
      }
      $lastx = $x{$i,$j};
    }
  }
  for ($i=0,$n=0; $i<$numContours; ++$i) {
    for ($j=0; $j<$contourLen[$i]; ++$j,++$n) {
      if (($flags{$i,$j} & 0x20) && (($flags{$i,$j} & 0x04)==0)) {
        $y{$i,$j} = $lasty;
      } elsif ($flags{$i,$j} & 0x04) {
        $tmpy = uint8();
	if (($flags{$i,$j} & 0x20)==0) { $tmpy = 0 - $tmpy; }
	$y{$i,$j} = $lasty + $tmpy;
      } else {
        $tmpy = int16();
        $y{$i,$j} = $lasty + $tmpy;
      }
      $lasty = $y{$i,$j};
    }
  }
  for ($i=0; $i<$numContours; ++$i) {
    for ($j=0; $j<$contourLen[$i]; ++$j) {
      print "$x{$i,$j}, $y{$i,$j}";
      if ($flags{$i,$j} & 0x01) { print " onCurve"; }
      print "\n";
    }
    print "end\n";
  }


close(TTF);

#
# END OF MAIN
#

sub str32 {
  my($ret);
  read(TTF,$ret,4);
  return($ret);
}

sub str16 {
  my($ret);
  read(TTF,$ret,2);
  return($ret);
}

sub uint32 {
  my($ret);
  read(TTF,$ret,4);
  return(unpack("N",$ret));
}

sub uint16 {
  my($ret);
  read(TTF,$ret,2);
  return(unpack("n",$ret));
}

sub uint8 {
  my($ret);
  read(TTF,$ret,1);
  return(unpack("C",$ret));
}

sub int16 {
  my($ret,$bits);
  read(TTF,$ret,2);
  $bits=unpack("n",$ret);
  if ($bits & 0x8000) {
    return(-65536+$bits);
  } else {
    return($bits);
  }
}

sub skip8 {
  read(TTF,$skip,1);
}

sub skip16 {
  read(TTF,$skip,2);
}

sub skip32 {
  read(TTF,$skip,4);
}

sub skip64 {
  read(TTF,$skip,8);
}


Post a reply to this message

Copyright 2003-2023 Persistence of Vision Raytracer Pty. Ltd.