Skip to content

Commit

Permalink
slight change to file format, fixed encoders and loaders to accommodate
Browse files Browse the repository at this point in the history
  • Loading branch information
lowfatcode committed Aug 29, 2022
1 parent 7649e3c commit c6b03c5
Show file tree
Hide file tree
Showing 53 changed files with 222 additions and 154 deletions.
22 changes: 22 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "C/C++: clang++ build and debug active file",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/build/${relativeFileDirname}/${fileBasenameNoExtension}",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": false,
"MIMode": "lldb",
"preLaunchTask": "C/C++: clang++ build active file",
"postDebugTask": "Open image"
}
]
}
41 changes: 41 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"type": "shell",
"label": "C/C++: clang++ build active file",
"command": "/usr/bin/clang++",
"args": [
"-std=c++17",
"-stdlib=libc++",
"-g",
"${file}",
"-I",
".",
"-I",
"${workspaceFolder}",
"-o",
"${workspaceFolder}/build/${relativeFileDirname}/${fileBasenameNoExtension}"
],
"options": {
"cwd": "${workspaceFolder}"
},
"problemMatcher": ["$gcc"],
"group": {
"kind": "build",
"isDefault": true
},
"detail": "Task generated by Debugger."
},
{
"type": "shell",
"label": "Open image",
"command": "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code",
"args": [
"/tmp/out.png"
]
}
]
}
50 changes: 21 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ Font data can be output either as a binary file or as source files for C(++) and
- `af`: generates a binary font file for embedding with your linker or loading at runtime from a filesystem.
- `c` generates a C(++) code file containing a const array of font data
- `python` generates a Python code file containing an array of font data
- `--scale`: the scale to apply to metrics and coordinates, must be a power of 2. (default: `128`)
- `--quality`: the quality of decomposed bezier curves, either `low`, `medium`, or `high` (default: `medium` - affects file size)

The list of characters to include can be specified in three ways:
Expand All @@ -85,17 +84,12 @@ The file `roboto-abcdefg.af` is now ready to embed into your project.

An Alright Fonts file consists of an 8-byte header, followed by a number of glyphs, followed by the contour data for the glyphs.

Glyph metrics and contour coordinates are either one or two bytes values depending on the `scale` used when encoding the font. This saves a lot of space if we know the coordinates can never exceed the range of a signed byte.

- `scale <= 7` - one byte metrics and coordinates (values between `-128..127`)
- `scale >= 8` - two byte metrics and coordinates (values between `-32768..32767`)

|size (bytes)|description|
|--:|---|
|`8`|header|
|`9` or `14`|glyph dictionary entry 1|
|`9` or `14`|glyph dictionary entry ..|
|`9` or `14`|glyph dictionary entry n|
|`9`|glyph dictionary entry 1|
|`9`|glyph dictionary entry ..|
|`9`|glyph dictionary entry n|
|variable|glyph 1 contours|
|variable|glyph .. contours|
|variable|glyph n contours|
Expand All @@ -108,20 +102,17 @@ The very basic header includes a magic marker for identification, the number of
|--:|---|---|---|
|`4`|`"af!?"`|bytes|magic marker bytes|
|`2`|`count`|unsigned 16-bit|number of glyphs in file|
|`1`|`scale`|unsigned 8-bit|log2 of scale factor for coordinates (e.g. `7` if the scaling factor is `128`)|
|`1`|`flags`|unsigned 8-bit|flags byte (reserved for future use)|
|`2`|`flags`|unsigned 16-bit|flags (reserved for future use)|

#### Coordinate normalisation and scaling

All coordinates of the glyph (bounding boxes, advances, and contour points) are divided by the largest dimension of the bounding box that contains all glyphs - effectively normalising them to a common `-1..1 x -1..1` box.

> Throughout the documentation we will assume that `scale` is `7` and therefore the coordinate range is `-128..127`. Depending on your needs you may use smaller or larger scales to trade off quality for size.
All coordinates of the glyph (bounding boxes, advances, and contour points) are divided by the largest dimension of the bounding box that contains all glyphs - effectively normalising them to a common `-1..1 x -1..1` box. The coordinates are then multiplied by `127` scaling them up to the range `-128` to `127`.

The coordinates are then scaled up by multiplying them by 2 to the power of `scale`. The default scale value is `7` allowing for coordinates ranging from `-128` to `127` - this value was chosen for a number of reasons:
This scale was chosen for a number of reasons:

- highest quality where contour coordinates encode as two bytes reducing file size
- provides good enough resolution to retain fine detail
- avoid expensive divide when scaling during rendering (`2^n` allows a cheap bitshift instead)
- contour coordinates encode as single bytes reducing file size
- provides good enough resolution to retain fine detail even on complex glyphs
- avoid expensive divide when scaling during rendering: `(size_px * coordinate) >> 8`

#### Flags

Expand All @@ -134,6 +125,7 @@ It's also likely that we may want, in future, to add a feature or two:
- excluding glyph bounding boxes
- including glyph pair kerning data
- allowing 4-byte character codepoints
- allow a finer scale for coordinates (i.e. `-65536..65535`)
- alternative packing methods for contour data
- better support for vertical or LTR languages

Expand All @@ -154,11 +146,11 @@ The glyphs are laid out one after another in the file:
|size (bytes)|name|type|notes|
|--:|---|---|---|
|`2`|`codepoint`|unsigned integer|utf-8 codepoint or ascii character code|
|`1` or `2`|`bbox_x`|signed integer|left edge of bounding box|
|`1` or `2`|`bbox_y`|signed integer|top edge of bounding box|
|`1` or `2`|`bbox_w`|unsigned integer|width of bounding box|
|`1` or `2`|`bbox_h`|unsigned integer|height of bounding box|
|`1` or `2`|`advance`|unsigned integer|horizontal or vertical advance|
|`1`|`bbox_x`|signed integer|left edge of bounding box|
|`1`|`bbox_y`|signed integer|top edge of bounding box|
|`1`|`bbox_w`|unsigned integer|width of bounding box|
|`1`|`bbox_h`|unsigned integer|height of bounding box|
|`1`|`advance`|unsigned integer|horizontal or vertical advance|
|`2`|`contour_size`|unsigned integer|length of contour data in bytes|

...and repeat for one entry per glyph.
Expand All @@ -174,11 +166,11 @@ To denote the end of the contours of a glyph there will be a 16-bit zero value -
|size (bytes)|name|type|notes|
|--:|---|---|---|
|`2`|`count`|unsigned 16-bit|count of coordinates in first contour|
|`1`, or `2`|`point 1 x`|signed integer|first point x component|
|`1`, or `2`|`point 1 y`|signed integer|first point y component|
|`1`|`point 1 x`|signed integer|first point x component|
|`1`|`point 1 y`|signed integer|first point y component|
||..|..|..|
|`1`, or `2`|`point n x`|signed integer|nth point x component|
|`1`, or `2`|`point n y`|signed integer|nth point y component|
|`1`|`point n x`|signed integer|nth point x component|
|`1`|`point n y`|signed integer|nth point y component|
|`2`|`count`|unsigned 16-bit|count of coordinates in second contour|
|..|..|..|..|
|`2`|`count`|unsigned 16-bit|0 value denotes end of contours for glyph|
Expand All @@ -190,7 +182,7 @@ To denote the end of the contours of a glyph there will be a 16-bit zero value -
Here three Alright Fonts files have been generated containing the full set of printable ASCII characters. The font used was Roboto Black and the command line parameters to `afinate` were:

```bash
./afinate --font fonts/Roboto-Black.ttf --scale 128 --quality [low|medium|high]
./afinate --font fonts/Roboto-Black.ttf --quality [low|medium|high]
```

|Low|Medium|High|
Expand All @@ -205,5 +197,5 @@ The differences are easier to see when viewing the images at their original size
You can pipe the output of `afinate` directly into the `render-demo` example script to product a swatch image.

```bash
./afinate --font fonts/Roboto-Black.ttf --scale 128 --quality high - | ./render-demo
./afinate --font fonts/Roboto-Black.ttf --quality high - | ./render-demo
```
21 changes: 3 additions & 18 deletions afinate
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,9 @@ from python_alright_fonts import Glyph, Point, Encoder
# parse command line arguments
# ===========================================================================

# validate supplied scale values to ensure they are powers of 2
class validate_scale(argparse.Action):
def __call__(self, parser, namespace, value, option_string=None):
requested_scale = value
valid = (value and (not(value & (value - 1))))
if not valid:
raise ValueError("`scale` must be a power of 2.")
setattr(namespace, self.dest, value)

parser = argparse.ArgumentParser(description="Create an Alright Font (.af) file containing the specified set of glyphs.")
parser.add_argument("--font", type=argparse.FileType("rb"), required=True, help="the font (.ttf or .otf) that you want to extract glyphs from")
parser.add_argument("--format", type=str, default="af", choices=["af", "c", "python"], help="the output format (either 'af', 'c', or 'python'")
parser.add_argument("--scale", type=int, default=128, action=validate_scale, help="the scale to apply to metrics and coordinates - must be a power of 2. (default: 128)")
parser.add_argument("--quality", type=str, choices=["low", "medium", "high"], default="medium", help="the quality of decomposed bezier curves - affects font file size. (default: \"medium\")")
parser.add_argument("--characters", type=str, help="the list of characters that you want to extract. (default: ASCII character set)")
parser.add_argument("--corpus", type=argparse.FileType("r"), help="corpus to select characters from")
Expand All @@ -50,7 +40,7 @@ quality_map = {
}

try:
encoder = Encoder(args.font, args.scale, quality=quality_map[args.quality])
encoder = Encoder(args.font, quality=quality_map[args.quality])
except:
print("Failed to load font - stopping.")
sys.exit(1)
Expand All @@ -64,12 +54,8 @@ except:
print(" - bounding box {}, {} -> {}, {}".format(
encoder.bbox_l, encoder.bbox_t, encoder.bbox_r, encoder.bbox_b))

# the largest bounding value gives us a scale factor that we can
# use to normalise all glyph contour data to a -1..1 range square
print(" - requested coordinate scale factor {}".format(args.scale))

# then upscale from the normalised scale to our preferred scale
print(" - combined scale factor {}".format(round(encoder.scale_factor, 3)))
print(" - scale factor {}".format(round(encoder.scale_factor, 3)))


# get the list of character code points or ascii character codes
Expand Down Expand Up @@ -126,8 +112,7 @@ print(" - header")
result = bytes()
result += b"af!?"
result += struct.pack(">H", len(encoder.glyphs))
result += struct.pack(">B", int(math.log2(args.scale)))
result += struct.pack(">B", 0) # no flags to set
result += struct.pack(">H", 0) # no flags to set

print(" - glyph dictionary")
for codepoint, glyph in encoder.glyphs.items():
Expand Down
Loading

0 comments on commit c6b03c5

Please sign in to comment.