Does anyone knows how to extract the characters image from a font(ttf) file?
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).
You can use GetGlyphOutline with GGO_BEZIER
to get the shape of a single character.
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
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:
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>
© 2022 - 2025 — McMap. All rights reserved.