从ttf文件中提取字体字符图像

发布于 2024-08-16 09:32:42 字数 31 浏览 4 评论 0原文

有谁知道如何从字体(ttf)文件中提取字符图像?

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

染年凉城似染瑾 2024-08-23 09:32:42

TTF 是一种矢量格式,因此实际上没有字符形状。加载字体,将其选择到设备上下文(内存上下文)中,渲染字符,抓取位图。

相关 API:AddFontResource、CreateFont、CreateDC、CreateBitmap、SelectObject、TextOut(或 DrawText)。

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).

和我恋爱吧 2024-08-23 09:32:42

您可以将 GetGlyphOutlineGGO_BEZIER 结合使用 获取单个字符的形状。

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

像极了他 2024-08-23 09:32:42

为了完整起见,我想向这个相当古老的线程添加 GUI 和 Python 方式。

如果目标是从 .ttf 文件中提取图像(例如 png),我发现了两种非常直接的方法,它们都涉及开源程序 fontforge (链接到他们的网站):

  • GUI方式(适合提取少量字符):打开.ttf文件在 fontforge 中单击您要导出的字符。然后:文件->导出->格式:png
  • CLI / Python 方式(适合自动化):FontForge 有一个适用于 python 2.7 的 cli api,它允许自动提取图像。请参阅此超级用户线程了解一个完整的脚本。

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

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

两人的回忆 2024-08-23 09:32:42

要获取图像,首要任务是处理 glyf 表。您需要找到一种方法来解析该表。

假设您可以从 ttf 完全解析它,有几种情况:

  1. 简单字形< /a>
  2. 复合字形

以下讨论将重点关注简单字形

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
}

对于每个简单字形,您可以总结以下信息:

NumberOfContours, Point{X, Y, Flags}

有了这些信息,您就可以开始绘图了。参考如下方法进行绘制:

<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>

当你有了点和 onCurve 信息后,你实际上可以通过观察和实验来推断它。您可以修改以下信息来自由绘制一些形状。

<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>

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>

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文