extract font character image from ttf file
Asked Answered
T

4

5

Does anyone knows how to extract the characters image from a font(ttf) file?

Tyner answered 4/1, 2010 at 22:5 Comment(0)
A
6

TTF is a vector format, so there are no characters shapes, really. Load the font, select it into a device context (a memory one), render a character, grab a bitmap.

Relevant APIs: AddFontResource, CreateFont, CreateDC, CreateBitmap, SelectObject, TextOut (or DrawText).

Acquirement answered 4/1, 2010 at 22:8 Comment(0)
W
2

You can use GetGlyphOutline with GGO_BEZIER to get the shape of a single character.

Wintertime answered 4/1, 2010 at 22:18 Comment(1)
do you know the same function in C#?Tyner
I
2

For the sake of completeness I'd like to add a GUI and Python way to this pretty old thread.

If the goal is to extract images (as e.g. png) from a .ttf file I found two pretty straight forward ways which both involve the open-source program fontforge (Link to their website):

  • GUI Way (Suitable for extracting a handful of characters): Open the .ttf file in fontforge click on the character you want to export. Then: file -> export -> format:png
  • CLI / Python Way (Suitable for automation): FontForge has a cli api for python 2.7 which allows to automate the extraction of the images. Refer to this superuser thread for a complete script.

Link 1: https://fontforge.org/en-US/
Link 2: https://superuser.com/questions/1337567/how-do-i-convert-a-ttf-into-individual-png-character-images

Isaiasisak answered 9/10, 2020 at 8:29 Comment(0)
K
0

To obtain the image, the primary task is to process the glyf table. You need to find a way to parse this table.

Assuming you can fully parse it form ttf, there are a few scenarios:

  1. Simple Glyph
  2. Composite Glyph

The following discussion will focus on Simple Glyph.

type SimpleGlyph struct {
    Header struct {
        NumberOfContours int16
        XMin             int16
        YMin             int16
        XMax             int16
        YMax             int16
    }
    EndPtsOfContours  []uint16
    InstructionLength uint16
    Instructions      []uint8 // Related to font hinting (affects small font rendering)
    Flags []struct { // It is actually 1 byte, for easier reading, I turned it into this struct
        OnCurve                       bool // bit 0 // In Go language, bool is a byte
        XShortVector                  bool // bit 1
        YShortVector                  bool // bit 2
        Repeat                        bool // bit 3
        XIsSameOrPositiveXShortVector bool // bit 4
        YIsSameOrPositiveYShortVector bool // bit 5
        OverlapSimple                 bool // bit 6
    }
    // You need to parse this to get X, Y. The actual data might be 1 byte or 2 bytes. You need to rely on flags to determine how many bytes to read. For convenience, we use larger spaces to store them.
    XCoordinates []int16 
    YCoordinates []int16
}

For each Simple Glyph, you can summarize the following information:

NumberOfContours, Point{X, Y, Flags}

With this information, you can start drawing. Refer to the following method for drawing:

<body></body>

<script>
  function drawPath(data) {
    const paths = []
    const contours = Object.groupBy(data, ({ContourIdx}) => {
      return ContourIdx
    })

    for (const [_, points] of Object.entries(contours)) {
      let d = ""
      for (let i = 0; i < points.length; i++) {

        const pt = points[i]

        if (i === 0) {
          d += `M ${pt.X} ${pt.Y * -1}`
          continue
        }

        if (pt.OnCurve) {
          d += ` L ${pt.X} ${pt.Y * -1}`
        } else {
          if (points[i - 1].OnCurve) {
            d += ` Q ${pt.X} ${pt.Y * -1}`
          } else {
            d += ` ${pt.X} ${pt.Y * -1}`
          }
        }
      }
      d += " Z"
      paths.push(d)
    }
    return paths
  }
</script>

<script>
  // data from glyph Table
  const data = [
    {"ContourIdx": 0, "PtIdx": 0, "X": 1346, "Y": 53, "OnCurve": true},
    {"ContourIdx": 0, "PtIdx": 1, "X": 1190, "Y": -27, "OnCurve": true},
    {"ContourIdx": 0, "PtIdx": 2, "X": 1186, "Y": -16, "OnCurve": false},
    {"ContourIdx": 0, "PtIdx": 3, "X": 1069, "Y": 260, "OnCurve": false},

    {"ContourIdx": 0, "PtIdx": 4, "X": 973, "Y": 477, "OnCurve": true},
    {"ContourIdx": 0, "PtIdx": 5, "X": 375, "Y": 477, "OnCurve": true},
    {"ContourIdx": 0, "PtIdx": 6, "X": 270, "Y": 244, "OnCurve": false},
    {"ContourIdx": 0, "PtIdx": 7, "X": 156, "Y": -12, "OnCurve": false},

    {"ContourIdx": 0, "PtIdx": 8, "X": 150, "Y": -27, "OnCurve": true},
    {"ContourIdx": 0, "PtIdx": 9, "X": 16, "Y": 45, "OnCurve": true},
    {"ContourIdx": 0, "PtIdx": 10, "X": 42, "Y": 97, "OnCurve": false},
    {"ContourIdx": 0, "PtIdx": 11, "X": 419, "Y": 910, "OnCurve": false},

    {"ContourIdx": 0, "PtIdx": 12, "X": 622, "Y": 1358, "OnCurve": true},
    {"ContourIdx": 0, "PtIdx": 13, "X": 752, "Y": 1358, "OnCurve": true},
    {"ContourIdx": 0, "PtIdx": 14, "X": 952, "Y": 910, "OnCurve": false},
    {"ContourIdx": 0, "PtIdx": 15, "X": 1338, "Y": 70, "OnCurve": false},


    {"ContourIdx": 1, "PtIdx": 16, "X": 916, "Y": 605, "OnCurve": true},
    {"ContourIdx": 1, "PtIdx": 17, "X": 862, "Y": 727, "OnCurve": false},
    {"ContourIdx": 1, "PtIdx": 18, "X": 742, "Y": 993, "OnCurve": false},

    {"ContourIdx": 1, "PtIdx": 19, "X": 676, "Y": 1138, "OnCurve": true},
    {"ContourIdx": 1, "PtIdx": 20, "X": 672, "Y": 1138, "OnCurve": true},
    {"ContourIdx": 1, "PtIdx": 21, "X": 621, "Y": 1028, "OnCurve": false},
    {"ContourIdx": 1, "PtIdx": 22, "X": 477, "Y": 707, "OnCurve": false},

    {"ContourIdx": 1, "PtIdx": 23, "X": 430, "Y": 605, "OnCurve": true},
    {"ContourIdx": 1, "PtIdx": 24, "X": 431, "Y": 604, "OnCurve": true},
    {"ContourIdx": 1, "PtIdx": 25, "X": 915, "Y": 604, "OnCurve": true},
  ]

  const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
  svg.setAttribute("width", "2048") // size from head.UnitsPerEm : https://learn.microsoft.com/en-us/typography/opentype/spec/head
  svg.setAttribute("height", "2048")
  const g = document.createElementNS('http://www.w3.org/2000/svg', 'g')
  svg.append(g)
  g.setAttribute("stroke", "black")
  g.setAttribute('stroke-width', '1')
  g.setAttribute("fill", "transparent")
  g.setAttribute("style", "transform: translate(0, 2000px)")
  const paths = drawPath(data)
  for (const d of paths) {
    const path = document.createElementNS('http://www.w3.org/2000/svg', 'path')
    path.setAttributeNS(null, 'd', d)
    g.append(path)
  }
  document.body.append(svg)
</script>

When you have the point and the onCurve information, you can actually deduce it through observation and experimentation. You can modify the following information to draw some shapes freely.

<body>
<svg width="2000" height="2000">
  <g stroke="black" fill="transparent" transform="translate(0 1900)">
  </g>
</svg><br>
</body>

<script>
  // M 1346 53 => M 1346 -53
  function inverseY(d) {
    return d.map(str => {
      let [type, ...ss] = str.split(" ")
      if (ss.length > 1) {
        ss = ss.map((e, i)=> {
          if (i % 2 === 1) {
            return `${Number(e) * -1}`
          }
          return e
        })
      }
      return [type, ...ss].join(" ")
    })
  }

  for (const contour of [
    [
      "M 1346 53",
      "L 1190 -27",
      "Q 1186 -16 1069 260 ",

      "L 973 477",
      "L 375 477",
      "Q 270 244 156 -12 ",

      "L 150 -27",
      "L 16 45",
      "Q 42 97 419 910 ",

      "L 622 1358",
      "L 752 1358",
      "Q 952 910 1338 70",
      "Z"
    ],
    [
      "M 916 605 ",
      "Q 862 727 742 993",

      "L 676 1138",
      "L 672 1138 ",
      "Q 621 1028 477 707",

      "L 430 605",
      "L 431 604",
      "L 915 604",
      "Z"
    ]
  ]) {
    const path = document.createElementNS('http://www.w3.org/2000/svg', 'path')
    path.setAttribute('d', inverseY(contour).join(" "))
    document.querySelector("g").append(path)
  }
</script>
Karakul answered 22/5, 2024 at 9:23 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.