├── .github
└── workflows
│ └── build.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── Makefile
├── README.md
├── TODO.md
├── VisualC
├── schrift.sln
├── schrift.vcxproj
├── schrift.vcxproj.filters
└── stress
│ ├── stress.vcxproj
│ └── stress.vcxproj.filters
├── config.mk
├── demo.c
├── libschrift.pc.in
├── resources
├── FiraGO-Regular.ttf
├── FiraGO-Regular_extended_with_NotoSansEgyptianHieroglyphs-Regular.ttf
├── NotoSansEgyptianHieroglyphs-Regular.ttf
├── Ubuntu-Regular.ttf
├── demo-screenshot.png
└── glass.utf8
├── schrift.3
├── schrift.c
├── schrift.h
├── stress.c
└── util
├── arg.h
└── utf8_to_utf32.h
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | branches: [ master ]
4 | pull_request:
5 |
6 | jobs:
7 | linux-build:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@v3
11 | - run: make
12 | macos-build:
13 | runs-on: macos-latest
14 | steps:
15 | - uses: actions/checkout@v3
16 | - run: make
17 | windows-build:
18 | runs-on: windows-latest
19 | steps:
20 | - uses: actions/checkout@v3
21 | - uses: ilammy/msvc-dev-cmd@v1
22 | - run: |
23 | cl /W4 /c /EHsc schrift.c
24 | lib schrift.obj
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.o
2 | libschrift.a
3 | /demo
4 | /stress
5 | schrift-*.tar.gz
6 |
7 | .vs/
8 | *.vcxproj.user
9 | Debug/
10 | Release/
11 | *.obj
12 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## v0.10.2
4 | - Support for Unicode codepoints outside of the Basic Multilingual Plane.
5 | Things like Hieroglyphs should work now! (Courtesy of `@flying-kestrel`!)
6 |
7 | ## v0.10.1
8 | - Fixed build on Mac OS (correct integer type usage)
9 | - Basic CI integration
10 |
11 | ## v0.10.0
12 | - There has been a large overhaul of the public API.
13 | Most notably the function `sft_char` has been replaced by a set of more fine-grained functions,
14 | namely `sft_lookup`, `sft_gmetrics`, and `sft_render`.
15 | Explanations of these new functions' semantics can be found in the man page.
16 | - struct types are now typedef'd, so you can now drop the `struct` keyword before libschrift's compound types.
17 | - The demo program received a complete rewrite courtesy of Andor Badi.
18 |
19 | ## v0.9.1
20 | For any user of the library, nothing should have changed at all since v0.9.0, except that
21 | performance should be noticeably improved.
22 |
23 | - Cleaned up the internals. For example, the struct buffer row pointer array is now gone.
24 | Instead, we just use a flat cell array.
25 | - More suitable tesselation heuristic. The old one was based on the distance of the middle
26 | control point to the halfway point between the two other control points.
27 | The new one is instead based on the area of the triangles that the three points generate.
28 | - A lot of minor performance improvements building up to a relatively significant speedup.
29 | Mostly achieved by reducing branch mispredictions and reordering or removing pipeline hazards
30 | in the raycaster and the post-processor.
31 |
32 | ## v0.9.0
33 | - Improved/fixed glyph positioning calculations.
34 | - Perform less internal heap allocations when rendering small glyphs.
35 | - Hardening against integer overflow bugs.
36 | - Explained the details of `sftdemo`.
37 | - Replaced the ill-suited aa-tree in `sftdemo` with a simple bitfield.
38 |
39 | ## v0.8.0
40 | - Slightly changed the missing glyph interface (check the man page for details).
41 | - Makefile config for building on OpenBSD.
42 | - Less reliance on undefined behaviour.
43 | - Ported the core libary & stress app to Microsoft Windows / MSVC.
44 | - (Hopefully) correct left side bearing calculations.
45 | - Wrapped the header in extern "C" so the library can be used from C++.
46 |
47 | ## v0.7.1
48 | - Pushed quantization to a later phase.
49 | Should get rid of any quantization errors (i.e. not quite black backgrounds).
50 | - Overall performance optimizations.
51 |
52 | ## v0.7.0
53 | - Fixed a left side bearing issue.
54 | - Fixed a situation where the `assert` in `decode_contours` could be triggered.
55 | - Fields in `SFT_Char` that are not set by `sft_char` will now always be initialized with zeros.
56 | - Documented `sft_linemetrics` and `sft_kerning` in the man page.
57 | - All outlines are now decoded before tesselation and rasterization are done in a single pass each.
58 | - Optimized `clip_points`
59 | - Changed the internal outline data-structure to be index-based, which also allowed untangling the
60 | critical path into a series of small data transformations. This has a huge amount of benefits:
61 | * The internal architecture is now much nicer
62 | * Much better debuggability all-round
63 | * Many new optimization possibilities have opened up
64 | * Less memory consumption than before
65 | * A pleasant 15% speed-boost for `sft_char`!
66 |
67 | This is the first version to have a changelog.
68 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | ISC License
2 |
3 | © 2019-2022 Thomas Oltmann and contributors
4 |
5 | Permission to use, copy, modify, and/or distribute this software for any
6 | purpose with or without fee is hereby granted, provided that the above
7 | copyright notice and this permission notice appear in all copies.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 |
17 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # See LICENSE file for copyright and license details.
2 |
3 | .POSIX:
4 |
5 | include config.mk
6 |
7 | VERSION=0.10.2
8 |
9 | .PHONY: all clean install uninstall dist
10 |
11 | all: libschrift.a libschrift.pc demo stress
12 |
13 | libschrift.a: schrift.o
14 | $(AR) rc $@ schrift.o
15 | $(RANLIB) $@
16 | schrift.o: schrift.h
17 |
18 | libschrift.pc: libschrift.pc.in
19 | @sed 's,@prefix@,$(PREFIX),;s,@version@,$(VERSION),' libschrift.pc.in > $@
20 |
21 | demo: demo.o libschrift.a
22 | $(LD) $(EXTRAS_LDFLAGS) $@.o -o $@ -L$(X11LIB) -L. -lX11 -lXrender -lschrift -lm
23 | demo.o: demo.c schrift.h util/utf8_to_utf32.h
24 | $(CC) -c $(EXTRAS_CFLAGS) $(@:.o=.c) -o $@ $(EXTRAS_CPPFLAGS) -I$(X11INC)
25 |
26 | stress: stress.o libschrift.a
27 | $(LD) $(EXTRAS_LDFLAGS) $@.o -o $@ -L. -lschrift -lm
28 | stress.o: stress.c schrift.h util/arg.h
29 | $(CC) -c $(EXTRAS_CFLAGS) $(@:.o=.c) -o $@ $(EXTRAS_CPPFLAGS)
30 |
31 | clean:
32 | rm -f *.o
33 | rm -f util/*.o
34 | rm -f libschrift.a
35 | rm -f demo
36 | rm -f stress
37 |
38 | install: libschrift.a libschrift.pc schrift.h schrift.3
39 | # libschrift.a
40 | mkdir -p "$(DESTDIR)$(PREFIX)/lib"
41 | cp -f libschrift.a "$(DESTDIR)$(PREFIX)/lib"
42 | chmod 644 "$(DESTDIR)$(PREFIX)/lib/libschrift.a"
43 | # libschrift.pc
44 | mkdir -p "$(DESTDIR)$(PREFIX)/lib/pkgconfig"
45 | cp -f libschrift.pc "$(DESTDIR)$(PREFIX)/lib/pkgconfig"
46 | chmod 644 "$(DESTDIR)$(PREFIX)/lib/pkgconfig/libschrift.pc"
47 | # schrift.h
48 | mkdir -p "$(DESTDIR)$(PREFIX)/include"
49 | cp -f schrift.h "$(DESTDIR)$(PREFIX)/include"
50 | chmod 644 "$(DESTDIR)$(PREFIX)/include/schrift.h"
51 | # schrift.3
52 | mkdir -p "$(DESTDIR)$(MANPREFIX)/man3"
53 | cp schrift.3 "$(DESTDIR)$(MANPREFIX)/man3"
54 | chmod 644 "$(DESTDIR)$(MANPREFIX)/man3/schrift.3"
55 |
56 | uninstall:
57 | rm -f "$(DESTDIR)$(PREFIX)/lib/libschrift.a"
58 | rm -f "$(DESTDIR)$(PREFIX)/lib/pkgconfig/libschrift.pc"
59 | rm -f "$(DESTDIR)$(PREFIX)/include/schrift.h"
60 | rm -f "$(DESTDIR)$(MANPREFIX)/man3/schrift.3"
61 |
62 | dist:
63 | rm -rf "schrift-$(VERSION)"
64 | mkdir -p "schrift-$(VERSION)"
65 | cp -R README.md LICENSE CHANGELOG.md TODO.md schrift.3 \
66 | Makefile config.mk libschrift.pc.in \
67 | schrift.c schrift.h demo.c stress.c \
68 | resources/ util/ \
69 | "schrift-$(VERSION)"
70 | tar -cf - "schrift-$(VERSION)" | gzip -c > "schrift-$(VERSION).tar.gz"
71 | rm -rf "schrift-$(VERSION)"
72 |
73 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | libschrift
2 | ==========
3 | *libschrift* is a lightweight TrueType font rendering library.
4 |
5 | libschrift aims to:
6 | - Be as simple and easy-to-use as possible.
7 | See:
8 | - Make correct (as in artifact-free, Unicode-aware etc.)
9 | font rendering easy to achieve.
10 | - Be reasonably secure, which especially means to not crash,
11 | leak memory / resources or expose major security
12 | vulnerabilities on corrupted / malicious / random inputs.
13 |
14 | Features
15 | --------
16 | - Unicode support
17 | - Very small! (~1500 LoC)
18 | - No dependencies on anything other than the C standard library
19 | (POSIX / WINAPI are only used for memory mapping, which is not essential)
20 | - Compatible with any C99 compiler
21 | - Highly portable! Some versions have been tested on
22 | Linux, OpenBSD, FreeBSD, Windows, iOS, and macOS!
23 |
24 | Limitations
25 | -----------
26 | - Unicode is the only supported text encoding.
27 | - Support for most TrueType (.ttf) and certain OpenType (.otf) fonts.
28 | No bitmap or PostScript fonts.
29 | - No hinting. Especially no auto-hinting as found in FreeType2.
30 | - No color emojis.
31 |
32 | Building
33 | --------
34 | On **Unix-like systems** including Mac OS, you can use `make && make install`
35 | to install libschrift as a system-wide library. Since it is statically linked,
36 | you only need the library files for development.
37 | Users of your projects do not need to install libschrift.
38 |
39 | On **Windows**, you can either use the provided MSVC project,
40 | or compile with make via MSYS, or simply compile manually.
41 |
42 | **Alternatively**, you can always copy-paste `schrift.c` and `schrift.h` into your
43 | own projects source folder.
44 |
45 | Documentation
46 | -------------
47 | For documentation on how to use libschrift in your own projects,
48 | refer to the *schrift(3)* man page,
49 | the source code of the bundled *demo*,
50 | as well as the header file *schrift.h*.
51 |
52 | You can view the man page in your browser at
53 |
54 |
55 | Progress
56 | --------
57 | In terms of both security and performance, libschrift is already pretty solid.
58 | However, it is still missing some important features, like right-to-left text support.
59 |
60 | Visual Quality
61 | --------------
62 | A screenshot of the demo program:
63 | 
64 |
65 | Contributing
66 | ------------
67 | Bug Reports, Suggestions, Questions, Criticism, Pull Requests and Patches are all welcome!
68 |
69 | You can also support the project by buying me (Thomas Oltmann) a coffee, if you'd like:
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 | # schrift To-Do's
2 | The following bullet points are listed in no particular order.
3 |
4 | ## Bugs
5 | - Romanian S-comma (Unicode: `U+0219`) is apparently not displayed.
6 | `AnonPro` is a font that *should* have an outline for this character.
7 |
8 | ## API redesign
9 | - Instead of scales and offsets, the user will be able to directly supply a 2x3 matrix. There will be some convenience macros
10 | for constructing such a matrix from simpler parameters, like from a uniform scale alone.
11 | - Anything like `sft_char` should take a flags parameter, so the `flags` field in struct SFT can be removed.
12 |
13 | ## Features
14 | - Kerning needs to be tested.
15 | - `stress` should render a large range of code points to be more representative.
16 | - We will probably need user-defined transformations for slanted text etc.
17 | This should be pretty easy now since we already pass generic affine linear transformation matrices around everywhere.
18 | - We need an interface for subpixel rendering.
19 | Mattias Andrée quite early on proposed a possible API for this.
20 | - There are some kerning features like minimum values that are not yet supported.
21 | - There are some compound glyph features like grid-snapping that are not yet supported.
22 | - We need an interface to support using an entire priority / codepoint-range based stack of fonts.
23 | For the longest time, I thought this could be done by a text shaping library sitting on top of schrift.
24 | Now I'm starting to see that it would be much more beneficial to implement this directly within libschrift.
25 | - Consider internally switching to Q16.16 (or Q12.20) fixnums for most rational number representations.
26 | This is practically neccessary to get good performance on CPUs without FPU, but on any system that has one
27 | it will probably incur a big speed hit ...
28 | - right-to-left text support.
29 |
30 | ## Demo Applications
31 | - A separate demo that works on Windows / non-X11 platforms.
32 | SDL2 seems like a good idea.
33 |
34 | ## Documentation
35 | - Example snippets in the man page.
36 |
37 | ## Code Quality
38 | - Refactor `simple_outline`
39 | - Refactor `tesselate_curves`
40 | - `transform_points` probably should take the outline as the primary argument.
41 | - `clip_points` probably should take the outline as the primary argument.
42 | - The following new functions to map array problems to single instance problems:
43 | * `decode_contours` / `decode_contour`
44 | - Think about using some internal type aliases to make the intentions behind the typing clearer.
45 | - struct outline is responsible for a lot of allocations, which could at least partially be done on the stack.
46 |
--------------------------------------------------------------------------------
/VisualC/schrift.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.28307.421
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "schrift", "schrift.vcxproj", "{65D14E02-EC19-49EB-8E43-F33B4B0F8286}"
7 | EndProject
8 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "stress", "stress\stress.vcxproj", "{3F6EDF46-E03C-4090-9388-2A1522149DEF}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|x64 = Debug|x64
13 | Debug|x86 = Debug|x86
14 | Release|x64 = Release|x64
15 | Release|x86 = Release|x86
16 | EndGlobalSection
17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
18 | {65D14E02-EC19-49EB-8E43-F33B4B0F8286}.Debug|x64.ActiveCfg = Debug|x64
19 | {65D14E02-EC19-49EB-8E43-F33B4B0F8286}.Debug|x64.Build.0 = Debug|x64
20 | {65D14E02-EC19-49EB-8E43-F33B4B0F8286}.Debug|x86.ActiveCfg = Debug|Win32
21 | {65D14E02-EC19-49EB-8E43-F33B4B0F8286}.Debug|x86.Build.0 = Debug|Win32
22 | {65D14E02-EC19-49EB-8E43-F33B4B0F8286}.Release|x64.ActiveCfg = Release|x64
23 | {65D14E02-EC19-49EB-8E43-F33B4B0F8286}.Release|x64.Build.0 = Release|x64
24 | {65D14E02-EC19-49EB-8E43-F33B4B0F8286}.Release|x86.ActiveCfg = Release|Win32
25 | {65D14E02-EC19-49EB-8E43-F33B4B0F8286}.Release|x86.Build.0 = Release|Win32
26 | {3F6EDF46-E03C-4090-9388-2A1522149DEF}.Debug|x64.ActiveCfg = Debug|x64
27 | {3F6EDF46-E03C-4090-9388-2A1522149DEF}.Debug|x64.Build.0 = Debug|x64
28 | {3F6EDF46-E03C-4090-9388-2A1522149DEF}.Debug|x86.ActiveCfg = Debug|Win32
29 | {3F6EDF46-E03C-4090-9388-2A1522149DEF}.Debug|x86.Build.0 = Debug|Win32
30 | {3F6EDF46-E03C-4090-9388-2A1522149DEF}.Release|x64.ActiveCfg = Release|x64
31 | {3F6EDF46-E03C-4090-9388-2A1522149DEF}.Release|x64.Build.0 = Release|x64
32 | {3F6EDF46-E03C-4090-9388-2A1522149DEF}.Release|x86.ActiveCfg = Release|Win32
33 | {3F6EDF46-E03C-4090-9388-2A1522149DEF}.Release|x86.Build.0 = Release|Win32
34 | EndGlobalSection
35 | GlobalSection(SolutionProperties) = preSolution
36 | HideSolutionNode = FALSE
37 | EndGlobalSection
38 | GlobalSection(ExtensibilityGlobals) = postSolution
39 | SolutionGuid = {0BF2EB9E-FE4D-4D22-B002-5FFF9C7BA804}
40 | EndGlobalSection
41 | EndGlobal
42 |
--------------------------------------------------------------------------------
/VisualC/schrift.vcxproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | Win32
7 |
8 |
9 | Release
10 | Win32
11 |
12 |
13 | Debug
14 | x64
15 |
16 |
17 | Release
18 | x64
19 |
20 |
21 |
22 | 15.0
23 | {65D14E02-EC19-49EB-8E43-F33B4B0F8286}
24 | Win32Proj
25 | 10.0.17763.0
26 |
27 |
28 |
29 | StaticLibrary
30 | true
31 | v141
32 |
33 |
34 | StaticLibrary
35 | false
36 | v141
37 |
38 |
39 | StaticLibrary
40 | true
41 | v141
42 |
43 |
44 | StaticLibrary
45 | false
46 | v141
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | true
68 |
69 |
70 | true
71 |
72 |
73 |
74 | WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)
75 | MultiThreadedDebugDLL
76 | Level3
77 | ProgramDatabase
78 | Disabled
79 |
80 |
81 | MachineX86
82 | true
83 | Windows
84 |
85 |
86 |
87 |
88 | WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)
89 | MultiThreadedDLL
90 | Level3
91 | ProgramDatabase
92 |
93 |
94 | MachineX86
95 | true
96 | Windows
97 | true
98 | true
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
--------------------------------------------------------------------------------
/VisualC/schrift.vcxproj.filters:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx
7 |
8 |
9 | {93995380-89BD-4b04-88EB-625FBE52EBFB}
10 | h;hh;hpp;hxx;hm;inl;inc;xsd
11 |
12 |
13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav
15 |
16 |
17 |
18 |
19 | Source Files
20 |
21 |
22 |
23 |
24 | Header Files
25 |
26 |
27 |
--------------------------------------------------------------------------------
/VisualC/stress/stress.vcxproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | Win32
7 |
8 |
9 | Release
10 | Win32
11 |
12 |
13 | Debug
14 | x64
15 |
16 |
17 | Release
18 | x64
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | 15.0
29 | {3F6EDF46-E03C-4090-9388-2A1522149DEF}
30 | Win32Proj
31 | stress
32 | 10.0.17763.0
33 |
34 |
35 |
36 | Application
37 | true
38 | v141
39 | Unicode
40 |
41 |
42 | Application
43 | false
44 | v141
45 | true
46 | Unicode
47 |
48 |
49 | Application
50 | true
51 | v141
52 | Unicode
53 |
54 |
55 | Application
56 | false
57 | v141
58 | true
59 | Unicode
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | false
81 |
82 |
83 | true
84 |
85 |
86 | true
87 |
88 |
89 | false
90 |
91 |
92 |
93 | NotUsing
94 | Level3
95 | MaxSpeed
96 | true
97 | true
98 | true
99 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
100 | true
101 | pch.h
102 | $(SolutionDir)\..;%(AdditionalIncludeDirectories)
103 |
104 |
105 | Console
106 | true
107 | true
108 | true
109 | schrift.lib;%(AdditionalDependencies)
110 | $(SolutionDir)\x64\Release;%(AdditionalLibraryDirectories)
111 |
112 |
113 |
114 |
115 | NotUsing
116 | Level3
117 | Disabled
118 | true
119 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
120 | true
121 | pch.h
122 | $(SolutionDir)\..;%(AdditionalIncludeDirectories)
123 |
124 |
125 | Console
126 | true
127 | schrift.lib;%(AdditionalDependencies)
128 | $(SolutionDir)\Debug;%(AdditionalLibraryDirectories)
129 |
130 |
131 |
132 |
133 | NotUsing
134 | Level3
135 | Disabled
136 | true
137 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions)
138 | true
139 | pch.h
140 | $(SolutionDir)\..;%(AdditionalIncludeDirectories)
141 |
142 |
143 | Console
144 | true
145 | schrift.lib;%(AdditionalDependencies)
146 | $(SolutionDir)\x64\Debug;%(AdditionalLibraryDirectories)
147 |
148 |
149 |
150 |
151 | NotUsing
152 | Level3
153 | MaxSpeed
154 | true
155 | true
156 | true
157 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
158 | true
159 | pch.h
160 | $(SolutionDir)\..;%(AdditionalIncludeDirectories)
161 |
162 |
163 | Console
164 | true
165 | true
166 | true
167 | schrift.lib;%(AdditionalDependencies)
168 | $(SolutionDir)\Release;%(AdditionalLibraryDirectories)
169 |
170 |
171 |
172 |
173 |
174 |
--------------------------------------------------------------------------------
/VisualC/stress/stress.vcxproj.filters:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx
7 |
8 |
9 | {93995380-89BD-4b04-88EB-625FBE52EBFB}
10 | h;hh;hpp;hxx;hm;inl;inc;ipp;xsd
11 |
12 |
13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
15 |
16 |
17 |
18 |
19 | Source Files
20 |
21 |
22 |
23 |
24 | Header Files
25 |
26 |
27 |
--------------------------------------------------------------------------------
/config.mk:
--------------------------------------------------------------------------------
1 | # Customize below to fit your system
2 |
3 | # compiler and linker
4 | CC = cc
5 | LD = cc
6 | AR = ar
7 | RANLIB = ranlib
8 |
9 | # installation paths
10 | PREFIX = /usr/local
11 | MANPREFIX = $(PREFIX)/share/man
12 |
13 | # compiler flags for libschrift
14 | CPPFLAGS =
15 | CFLAGS = -Os -std=c99 -pedantic -Wall -Wextra -Wconversion
16 | LDFLAGS = -Os
17 |
18 | # compiler flags for the demo & stress test applications
19 | EXTRAS_CPPFLAGS = -I./
20 | EXTRAS_CFLAGS = -g -Os -std=c99 -pedantic -Wall -Wextra
21 | EXTRAS_LDFLAGS = -g -Os
22 |
23 | # X11 API installation paths (needed by the demo)
24 | X11INC = /usr/include/X11
25 | X11LIB = /usr/lib/X11
26 | # Uncomment below to build the demo on OpenBSD
27 | #X11INC = /usr/X11R6/include
28 | #X11LIB = /usr/X11R6/lib
29 |
30 |
--------------------------------------------------------------------------------
/demo.c:
--------------------------------------------------------------------------------
1 | /*
2 | * A simple command line application that shows how to
3 | * use libschrift with X11 via XRender.
4 | * See LICENSE file for copyright and license details.
5 | * Contributed by Andor Badi.
6 | */
7 |
8 | #include
9 | #include
10 | #include
11 | #include
12 |
13 | #include
14 | #include "util/utf8_to_utf32.h"
15 | #include "schrift.h"
16 |
17 | static int add_glyph(Display *dpy, GlyphSet glyphset, SFT *sft, unsigned long cp)
18 | {
19 | #define ABORT(cp, m) do { fprintf(stderr, "codepoint 0x%04lX %s\n", cp, m); return -1; } while (0)
20 |
21 | SFT_Glyph gid; // unsigned long gid;
22 | if (sft_lookup(sft, cp, &gid) < 0)
23 | ABORT(cp, "missing");
24 |
25 | SFT_GMetrics mtx;
26 | if (sft_gmetrics(sft, gid, &mtx) < 0)
27 | ABORT(cp, "bad glyph metrics");
28 |
29 | SFT_Image img = {
30 | .width = (mtx.minWidth + 3) & ~3,
31 | .height = mtx.minHeight,
32 | };
33 | char pixels[img.width * img.height];
34 | img.pixels = pixels;
35 | if (sft_render(sft, gid, img) < 0)
36 | ABORT(cp, "not rendered");
37 |
38 | XGlyphInfo info = {
39 | .x = (short) -mtx.leftSideBearing,
40 | .y = (short) -mtx.yOffset,
41 | .width = (unsigned short) img.width,
42 | .height = (unsigned short) img.height,
43 | .xOff = (short) mtx.advanceWidth,
44 | .yOff = 0
45 | };
46 | Glyph g = cp;
47 | XRenderAddGlyphs(dpy, glyphset, &g, &info, 1,
48 | img.pixels, (int) (img.width * img.height));
49 |
50 | return 0;
51 | }
52 |
53 | int main()
54 | {
55 | #define END(m) do { fprintf(stderr, "%s\n", m); return 1; } while (0)
56 |
57 | Display *dpy = XOpenDisplay(NULL);
58 | if (dpy == NULL)
59 | END("Can't open X display");
60 | int s = 2, screen = DefaultScreen(dpy);
61 |
62 | Window win = XCreateWindow(dpy, DefaultRootWindow(dpy), 0, 0, 600*s, 440*s, 0,
63 | DefaultDepth(dpy, screen), InputOutput, CopyFromParent, 0, NULL);
64 | XSelectInput(dpy, win, ExposureMask);
65 | Atom wmDeleteWindow = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
66 | XSetWMProtocols(dpy, win, &wmDeleteWindow, 1);
67 | XMapRaised(dpy, win);
68 |
69 | XRenderPictFormat *fmt = XRenderFindVisualFormat(dpy, DefaultVisual(dpy, screen));
70 | Picture pic = XRenderCreatePicture(dpy, win, fmt, 0, NULL);
71 | XRenderColor fg = { 0, 0, 0, 0xFFFF }, bg = { 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF };
72 |
73 | Pixmap fgpix = XCreatePixmap(dpy, win, 1, 1, 24);
74 | XRenderPictureAttributes attr = { .repeat = True };
75 | fmt = XRenderFindStandardFormat(dpy, PictStandardRGB24);
76 | Picture fgpic = XRenderCreatePicture(dpy, fgpix, fmt, CPRepeat, &attr);
77 | XRenderFillRectangle(dpy, PictOpSrc, fgpic, &fg, 0, 0, 1, 1);
78 |
79 | fmt = XRenderFindStandardFormat(dpy, PictStandardA8);
80 | GlyphSet glyphset = XRenderCreateGlyphSet(dpy, fmt);
81 |
82 | SFT sft = {
83 | .xScale = 16*s,
84 | .yScale = 16*s,
85 | .flags = SFT_DOWNWARD_Y,
86 | };
87 | sft.font = sft_loadfile("resources/FiraGO-Regular_extended_with_NotoSansEgyptianHieroglyphs-Regular.ttf");
88 | if (sft.font == NULL)
89 | END("TTF load failed");
90 |
91 | XEvent event;
92 | while (!XNextEvent(dpy, &event)) {
93 | if (event.type == Expose) {
94 | XRenderFillRectangle(dpy, PictOpOver, pic, &bg, 0, 0, event.xexpose.width, event.xexpose.height);
95 |
96 | FILE *file = fopen("resources/glass.utf8", "r");
97 | if (file == NULL)
98 | END("Cannot open input text");
99 |
100 | SFT_LMetrics lmtx;
101 | sft_lmetrics(&sft, &lmtx);
102 | int y = 20 + lmtx.ascender + lmtx.lineGap;
103 |
104 | char text[256];
105 | while (fgets(text, sizeof(text), file)) {
106 | int n = strlen(text) - 1;
107 | text[n] = 0; // '\n' => len>0
108 |
109 | unsigned codepoints[sizeof(text)];
110 | n = utf8_to_utf32((unsigned char *) text, codepoints, sizeof(text)); // (const uint8_t *)
111 |
112 | for (int i = 0; i < n; i++) {
113 | add_glyph(dpy, glyphset, &sft, codepoints[i]);
114 | }
115 | XRenderCompositeString32(dpy, PictOpOver, fgpic, pic, NULL, glyphset, 0, 0, 20, y, codepoints, n);
116 |
117 | y += 2 * (lmtx.ascender + lmtx.descender + lmtx.lineGap);
118 | }
119 |
120 | fclose(file);
121 | } else if (event.type == ClientMessage) {
122 | if ((Atom) event.xclient.data.l[0] == wmDeleteWindow)
123 | break;
124 | }
125 | }
126 |
127 | sft_freefont(sft.font);
128 | XCloseDisplay(dpy);
129 | return 0;
130 | }
131 |
132 |
--------------------------------------------------------------------------------
/libschrift.pc.in:
--------------------------------------------------------------------------------
1 | prefix=@prefix@
2 | includedir=${prefix}/include
3 | libdir=${prefix}/lib
4 |
5 | Name: libschrift
6 | Description: A lightweight TrueType font rendering library
7 | Version: @version@
8 | Libs: -L${libdir} -lschrift
9 | Libs.private: -lm
10 | Cflags: -I${includedir}
11 |
--------------------------------------------------------------------------------
/resources/FiraGO-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomolt/libschrift/24737d2922b23df4a5692014f5ba03da0c296112/resources/FiraGO-Regular.ttf
--------------------------------------------------------------------------------
/resources/FiraGO-Regular_extended_with_NotoSansEgyptianHieroglyphs-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomolt/libschrift/24737d2922b23df4a5692014f5ba03da0c296112/resources/FiraGO-Regular_extended_with_NotoSansEgyptianHieroglyphs-Regular.ttf
--------------------------------------------------------------------------------
/resources/NotoSansEgyptianHieroglyphs-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomolt/libschrift/24737d2922b23df4a5692014f5ba03da0c296112/resources/NotoSansEgyptianHieroglyphs-Regular.ttf
--------------------------------------------------------------------------------
/resources/Ubuntu-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomolt/libschrift/24737d2922b23df4a5692014f5ba03da0c296112/resources/Ubuntu-Regular.ttf
--------------------------------------------------------------------------------
/resources/demo-screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomolt/libschrift/24737d2922b23df4a5692014f5ba03da0c296112/resources/demo-screenshot.png
--------------------------------------------------------------------------------
/resources/glass.utf8:
--------------------------------------------------------------------------------
1 | 1. Euro Symbol: €.
2 | 2. Greek: Μπορώ να φάω σπασμένα γυαλιά χωρίς να πάθω τίποτα.
3 | 3. Íslenska / Icelandic: Ég get etið gler án þess að meiða mig.
4 | 4. Polish: Mogę jeść szkło, i mi nie szkodzi.
5 | 5. Romanian: Pot să mănânc sticlă și ea nu mă rănește.
6 | 6. Ukrainian: Я можу їсти шкло, й воно мені не пошкодить.
7 | 7. Georgian: მინას ვჭამ და არა მტკივა.
8 | 8. Hindi: मैं काँच खा सकता हूँ, मुझे उस से कोई पीडा नहीं होती.
9 | 9. Hebrew(2): אני יכול לאכול זכוכית וזה לא מזיק לי.
10 | 10. Yiddish(2): איך קען עסן גלאָז און עס טוט מיר נישט װײ.
11 | 11. Arabic(2): أنا قادر على أكل الزجاج و هذا لا يؤلمني.
12 | 12. Thai: ฉันกินกระจกได้ แต่มันไม่ทำให้ฉันเจ็บ
13 | 13. Random Egyptian hieroglyphs: 𓀌 𓀹 𓁚 𓁭
14 |
15 | text examples from Frank da Cruz's sampler: http://kermitproject.org/utf8.html
16 |
--------------------------------------------------------------------------------
/schrift.3:
--------------------------------------------------------------------------------
1 | .Dd January 30, 2021
2 | .Dt SCHRIFT 3
3 | .Os
4 | .Sh NAME
5 | .Nm schrift ,
6 | .Nm libschrift
7 | .Nd Lightweight TrueType font rendering library
8 | .Sh SYNOPSIS
9 | .Lb libschrift
10 | .In schrift.h
11 | .Ft const char *
12 | .Fn sft_version "void"
13 | .Ft SFT_Font *
14 | .Fn sft_loadmem "const void *mem" "unsigned long size"
15 | .Ft SFT_Font *
16 | .Fn sft_loadfile "const char *filename"
17 | .Ft void
18 | .Fn sft_freefont "SFT_Font *font"
19 | .Ft int
20 | .Fn sft_lmetrics "const SFT *sft" "SFT_LMetrics *metrics"
21 | .Ft int
22 | .Fn sft_lookup "const SFT *sft" "SFT_UChar codepoint" "SFT_Glyph *glyph"
23 | .Ft int
24 | .Fn sft_gmetrics "const SFT *sft" "SFT_Glyph glyph" "SFT_GMetrics *metrics"
25 | .Ft int
26 | .Fn sft_kerning "const SFT *sft" "SFT_Glyph leftGlyph" "SFT_Glyph rightGlyph" "SFT_Kerning *kerning"
27 | .Ft int
28 | .Fn sft_render "const SFT *sft" "SFT_Glyph glyph" "SFT_Image image"
29 | .Sh DESCRIPTION
30 | The function
31 | .Fn sft_version
32 | may be called to determine the version of
33 | .Nm .
34 | Since
35 | .Nm
36 | uses semantic versioning, the returned string is of the form \(dqMAJOR.MINOR.PATCH\(dq.
37 | .Ss Loading fonts
38 | .Fn sft_loadfile
39 | will load a font from a given filename (by mapping it into memory),
40 | whereas
41 | .Fn sft_loadmem
42 | can be given an arbitrary memory address and size (in bytes).
43 | This allows loading fonts from ZIP file streams etc.
44 | .sp
45 | Once a particular font is no longer needed, its memory should be freed again by calling
46 | .Fn sft_freefont .
47 | If the font has been loaded with
48 | .Fn sft_loadmem ,
49 | the underlying memory region will have to be freed separately.
50 | .Ss the SFT structure
51 | Most functions take a
52 | .Vt SFT
53 | as their primary argument.
54 | This is a structure to be filled out by the caller.
55 | It bundles multiple commonly needed parameters.
56 | The fields are as follows:
57 | .Bl -tag -width 8
58 | .It Va font
59 | The font to render with
60 | .It Va xScale
61 | The width of one em-square in pixels
62 | .It Va yScale
63 | The height of one em-square in pixels
64 | .It Va xOffset
65 | The horizontal offset to be applied before rendering to an image
66 | (Useful for subpixel-accurate positioning)
67 | .It Va yOffset
68 | The vertical offset to be applied before rendering to an image
69 | (Useful for subpixel-accurate positioning)
70 | .It Va flags
71 | A bitfield of binary flags
72 | .El
73 | .sp
74 | If the
75 | .Dv SFT_DOWNWARD_Y
76 | flag is set, the Y axis is interpreted to point downwards.
77 | Per default, it points upwards.
78 | .Ss Glyph ids
79 | .Nm
80 | operates in terms of glyph ids (of type
81 | .Vt SFT_Glyph ) ,
82 | which are font-specific identifiers assigned to renderable symbols (glyphs).
83 | The way to obtain a glyph id is to call
84 | .Fn sft_lookup .
85 | This function takes a Unicode codepoint in
86 | .Va codepoint
87 | and writes its corresponding glyph id into the variable pointed to by
88 | .Va glyph .
89 | .Ss Querying metrics
90 | .Fn sft_lmetrics
91 | calculates the typographic metrics neccessary for laying out multiple lines of text.
92 | .sp
93 | This function writes its output into the structure pointed to by
94 | .Va metrics .
95 | The fields are as follows:
96 | .Bl -tag -width 8
97 | .It Va ascender
98 | The distance from the baseline to the visual top of the text
99 | .It Va descender
100 | The distance from the baseline to the visual bottom of the text
101 | .It Va lineGap
102 | The default amount of horizontal padding between consecutive lines
103 | .El
104 | .sp
105 | When displaying multiple glyphs in a line, you have to keep track of the pen position.
106 | .Fn sft_gmetrics
107 | tells you where to draw the next glyph with respect to the pen position,
108 | and how to update it after rendering the glyph.
109 | .sp
110 | This function writes its output into the structure pointed to by
111 | .Va metrics .
112 | The fields are as follows:
113 | .Bl -tag -width 8
114 | .It Va advanceWidth
115 | How much the pen position should advance to the right after rendering the glyph
116 | .It Va leftSideBearing
117 | The offset that should be applied along the X axis from the pen position to the edge of the glyph image
118 | .It Va yOffset
119 | An offset along the Y axis relative to the pen position that should be applied when
120 | displaying the glyph
121 | .It Va minWidth
122 | The minimum width that an image needs such that the glyph can be properly rendered into it
123 | .It Va minHeight
124 | The minimum height that an image needs such that the glyph can be properly rendered into it
125 | .El
126 | .sp
127 | Some sequences of glyphs may look awkward if they're layed out naively.
128 | For example, the gap between the two characters \(dqVA\(dq might look disproportionally large.
129 | Kerning is a way to combat this artifact, by slightly moving the second character closer or further
130 | away by a small amount.
131 | .Fn sft_kerning
132 | may be used to retrieve kerning information for a given pair of glyph ids.
133 | .sp
134 | This function writes its output into the structure pointed to by
135 | .Va kerning .
136 | The fields are as follows:
137 | .Bl -tag -width 8
138 | .It Va xShift
139 | An amount that should be added to the pen's X position in-between the two glyphs
140 | .It Va yShift
141 | An amount that should be added to the pen's Y position in-between the two glyphs
142 | .El
143 | .Ss Rendering glyphs
144 | To actually render a glyph into a easily-displayable raster image, use
145 | .Fn sft_render .
146 | .sp
147 | Other than most functions,
148 | .Fn sft_render
149 | takes a structure in
150 | .Va image
151 | that is to be filled out by the caller.
152 | The fields are as follows:
153 | .Bl -tag -width 8
154 | .It Va pixels
155 | A pointer to an array of bytes, width * height in size
156 | .It Va width
157 | The number of pixels in a row of the image
158 | .It Va height
159 | The number of pixels in a column of the image
160 | .El
161 | .sp
162 | The image will be rendered into the memory provided in
163 | .Va pixels .
164 | Each byte corresponds to one pixel,
165 | with rows of the image being directly adjacent in memory without padding between them.
166 | Glyphs are rendered \(dqwhite on black\(dq,
167 | meaning the background has a pixel value of 0,
168 | and the foreground goes up to a value of 255.
169 | Pixel values follow a linear color ramp, unlike conventional computer screens.
170 | That is to say, they are
171 | .Em not gamma-corrected .
172 | These properties make it very easy to use images rendered with
173 | .Fn sft_render
174 | as alpha masks.
175 | .Sh RETURN VALUES
176 | .Fn sft_loadmem
177 | and
178 | .Fn sft_loadfile
179 | return
180 | .Dv NULL
181 | on error.
182 | .sp
183 | .Fn sft_lmetrics ,
184 | .Fn sft_lookup ,
185 | .Fn sft_hmetrics ,
186 | .Fn sft_kerning ,
187 | .Fn sft_extents ,
188 | and
189 | .Fn sft_render
190 | all return 0 on success and -1 on error.
191 | .Sh EXAMPLES
192 | See the source code of the
193 | .Sy demo
194 | for a detailed example of real-world usage of
195 | .Nm .
196 | .Sh AUTHORS
197 | .An Thomas Oltmann Aq Mt thomas.oltmann.hhg@gmail.com
198 | .Sh CAVEATS
199 | The only text encoding that
200 | .Nm
201 | understands is Unicode.
202 | .sp
203 | Similarly, the only kind of font file supported right now
204 | are TrueType (.ttf) fonts (Some OpenType fonts might work too,
205 | as OpenType is effectively a superset of TrueType).
206 | .sp
207 | As of v0.10.2, there is no support for right-to-left scripts
208 | .Em yet .
209 | .sp
210 | .Nm
211 | currently does not implement font hinting and probably never will.
212 |
--------------------------------------------------------------------------------
/schrift.c:
--------------------------------------------------------------------------------
1 | /* This file is part of libschrift.
2 | *
3 | * © 2019-2022 Thomas Oltmann and contributors
4 | *
5 | * Permission to use, copy, modify, and/or distribute this software for any
6 | * purpose with or without fee is hereby granted, provided that the above
7 | * copyright notice and this permission notice appear in all copies.
8 | *
9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
16 |
17 | #include
18 | #include
19 | #include
20 | #include
21 | #include
22 | #include
23 |
24 | #if defined(_MSC_VER)
25 | # define restrict __restrict
26 | #endif
27 |
28 | #if defined(_WIN32)
29 | # define WIN32_LEAN_AND_MEAN 1
30 | # include
31 | #else
32 | # define _POSIX_C_SOURCE 1
33 | # include
34 | # include
35 | # include
36 | # include
37 | #endif
38 |
39 | #include "schrift.h"
40 |
41 | #define SCHRIFT_VERSION "0.10.2"
42 |
43 | #define FILE_MAGIC_ONE 0x00010000
44 | #define FILE_MAGIC_TWO 0x74727565
45 |
46 | #define HORIZONTAL_KERNING 0x01
47 | #define MINIMUM_KERNING 0x02
48 | #define CROSS_STREAM_KERNING 0x04
49 | #define OVERRIDE_KERNING 0x08
50 |
51 | #define POINT_IS_ON_CURVE 0x01
52 | #define X_CHANGE_IS_SMALL 0x02
53 | #define Y_CHANGE_IS_SMALL 0x04
54 | #define REPEAT_FLAG 0x08
55 | #define X_CHANGE_IS_ZERO 0x10
56 | #define X_CHANGE_IS_POSITIVE 0x10
57 | #define Y_CHANGE_IS_ZERO 0x20
58 | #define Y_CHANGE_IS_POSITIVE 0x20
59 |
60 | #define OFFSETS_ARE_LARGE 0x001
61 | #define ACTUAL_XY_OFFSETS 0x002
62 | #define GOT_A_SINGLE_SCALE 0x008
63 | #define THERE_ARE_MORE_COMPONENTS 0x020
64 | #define GOT_AN_X_AND_Y_SCALE 0x040
65 | #define GOT_A_SCALE_MATRIX 0x080
66 |
67 | /* macros */
68 | #define MIN(a, b) ((a) < (b) ? (a) : (b))
69 | #define SIGN(x) (((x) > 0) - ((x) < 0))
70 | /* Allocate values on the stack if they are small enough, else spill to heap. */
71 | #define STACK_ALLOC(var, type, thresh, count) \
72 | type var##_stack_[thresh]; \
73 | var = (count) <= (thresh) ? var##_stack_ : calloc(sizeof(type), count);
74 | #define STACK_FREE(var) \
75 | if (var != var##_stack_) free(var);
76 |
77 | enum { SrcMapping, SrcUser };
78 |
79 | /* structs */
80 | typedef struct Point Point;
81 | typedef struct Line Line;
82 | typedef struct Curve Curve;
83 | typedef struct Cell Cell;
84 | typedef struct Outline Outline;
85 | typedef struct Raster Raster;
86 |
87 | struct Point { double x, y; };
88 | struct Line { uint_least16_t beg, end; };
89 | struct Curve { uint_least16_t beg, end, ctrl; };
90 | struct Cell { double area, cover; };
91 |
92 | struct Outline
93 | {
94 | Point *points;
95 | Curve *curves;
96 | Line *lines;
97 | uint_least16_t numPoints;
98 | uint_least16_t capPoints;
99 | uint_least16_t numCurves;
100 | uint_least16_t capCurves;
101 | uint_least16_t numLines;
102 | uint_least16_t capLines;
103 | };
104 |
105 | struct Raster
106 | {
107 | Cell *cells;
108 | int width;
109 | int height;
110 | };
111 |
112 | struct SFT_Font
113 | {
114 | const uint8_t *memory;
115 | uint_fast32_t size;
116 | #if defined(_WIN32)
117 | HANDLE mapping;
118 | #endif
119 | int source;
120 |
121 | uint_least16_t unitsPerEm;
122 | int_least16_t locaFormat;
123 | uint_least16_t numLongHmtx;
124 | };
125 |
126 | /* function declarations */
127 | /* generic utility functions */
128 | static void *reallocarray(void *optr, size_t nmemb, size_t size);
129 | static inline int fast_floor(double x);
130 | static inline int fast_ceil (double x);
131 | /* file loading */
132 | static int map_file (SFT_Font *font, const char *filename);
133 | static void unmap_file(SFT_Font *font);
134 | static int init_font (SFT_Font *font);
135 | /* simple mathematical operations */
136 | static Point midpoint(Point a, Point b);
137 | static void transform_points(unsigned int numPts, Point *points, double trf[6]);
138 | static void clip_points(unsigned int numPts, Point *points, int width, int height);
139 | /* 'outline' data structure management */
140 | static int init_outline(Outline *outl);
141 | static void free_outline(Outline *outl);
142 | static int grow_points (Outline *outl);
143 | static int grow_curves (Outline *outl);
144 | static int grow_lines (Outline *outl);
145 | /* TTF parsing utilities */
146 | static inline int is_safe_offset(SFT_Font *font, uint_fast32_t offset, uint_fast32_t margin);
147 | static void *csearch(const void *key, const void *base,
148 | size_t nmemb, size_t size, int (*compar)(const void *, const void *));
149 | static int cmpu16(const void *a, const void *b);
150 | static int cmpu32(const void *a, const void *b);
151 | static inline uint_least8_t getu8 (SFT_Font *font, uint_fast32_t offset);
152 | static inline int_least8_t geti8 (SFT_Font *font, uint_fast32_t offset);
153 | static inline uint_least16_t getu16(SFT_Font *font, uint_fast32_t offset);
154 | static inline int_least16_t geti16(SFT_Font *font, uint_fast32_t offset);
155 | static inline uint_least32_t getu32(SFT_Font *font, uint_fast32_t offset);
156 | static int gettable(SFT_Font *font, char tag[4], uint_fast32_t *offset);
157 | /* codepoint to glyph id translation */
158 | static int cmap_fmt4(SFT_Font *font, uint_fast32_t table, SFT_UChar charCode, uint_fast32_t *glyph);
159 | static int cmap_fmt6(SFT_Font *font, uint_fast32_t table, SFT_UChar charCode, uint_fast32_t *glyph);
160 | static int glyph_id(SFT_Font *font, SFT_UChar charCode, uint_fast32_t *glyph);
161 | /* glyph metrics lookup */
162 | static int hor_metrics(SFT_Font *font, uint_fast32_t glyph, int *advanceWidth, int *leftSideBearing);
163 | static int glyph_bbox(const SFT *sft, uint_fast32_t outline, int box[4]);
164 | /* decoding outlines */
165 | static int outline_offset(SFT_Font *font, uint_fast32_t glyph, uint_fast32_t *offset);
166 | static int simple_flags(SFT_Font *font, uint_fast32_t *offset, uint_fast16_t numPts, uint8_t *flags);
167 | static int simple_points(SFT_Font *font, uint_fast32_t offset, uint_fast16_t numPts, uint8_t *flags, Point *points);
168 | static int decode_contour(uint8_t *flags, uint_fast16_t basePoint, uint_fast16_t count, Outline *outl);
169 | static int simple_outline(SFT_Font *font, uint_fast32_t offset, unsigned int numContours, Outline *outl);
170 | static int compound_outline(SFT_Font *font, uint_fast32_t offset, int recDepth, Outline *outl);
171 | static int decode_outline(SFT_Font *font, uint_fast32_t offset, int recDepth, Outline *outl);
172 | /* tesselation */
173 | static int is_flat(Outline *outl, Curve curve);
174 | static int tesselate_curve(Curve curve, Outline *outl);
175 | static int tesselate_curves(Outline *outl);
176 | /* silhouette rasterization */
177 | static void draw_line(Raster buf, Point origin, Point goal);
178 | static void draw_lines(Outline *outl, Raster buf);
179 | /* post-processing */
180 | static void post_process(Raster buf, uint8_t *image);
181 | /* glyph rendering */
182 | static int render_outline(Outline *outl, double transform[6], SFT_Image image);
183 |
184 | /* function implementations */
185 |
186 | const char *
187 | sft_version(void)
188 | {
189 | return SCHRIFT_VERSION;
190 | }
191 |
192 | /* Loads a font from a user-supplied memory range. */
193 | SFT_Font *
194 | sft_loadmem(const void *mem, size_t size)
195 | {
196 | SFT_Font *font;
197 | if (size > UINT32_MAX) {
198 | return NULL;
199 | }
200 | if (!(font = calloc(1, sizeof *font))) {
201 | return NULL;
202 | }
203 | font->memory = mem;
204 | font->size = (uint_fast32_t) size;
205 | font->source = SrcUser;
206 | if (init_font(font) < 0) {
207 | sft_freefont(font);
208 | return NULL;
209 | }
210 | return font;
211 | }
212 |
213 | /* Loads a font from the file system. To do so, it has to map the entire font into memory. */
214 | SFT_Font *
215 | sft_loadfile(char const *filename)
216 | {
217 | SFT_Font *font;
218 | if (!(font = calloc(1, sizeof *font))) {
219 | return NULL;
220 | }
221 | if (map_file(font, filename) < 0) {
222 | free(font);
223 | return NULL;
224 | }
225 | if (init_font(font) < 0) {
226 | sft_freefont(font);
227 | return NULL;
228 | }
229 | return font;
230 | }
231 |
232 | void
233 | sft_freefont(SFT_Font *font)
234 | {
235 | if (!font) return;
236 | /* Only unmap if we mapped it ourselves. */
237 | if (font->source == SrcMapping)
238 | unmap_file(font);
239 | free(font);
240 | }
241 |
242 | int
243 | sft_lmetrics(const SFT *sft, SFT_LMetrics *metrics)
244 | {
245 | double factor;
246 | uint_fast32_t hhea;
247 | memset(metrics, 0, sizeof *metrics);
248 | if (gettable(sft->font, "hhea", &hhea) < 0)
249 | return -1;
250 | if (!is_safe_offset(sft->font, hhea, 36))
251 | return -1;
252 | factor = sft->yScale / sft->font->unitsPerEm;
253 | metrics->ascender = geti16(sft->font, hhea + 4) * factor;
254 | metrics->descender = geti16(sft->font, hhea + 6) * factor;
255 | metrics->lineGap = geti16(sft->font, hhea + 8) * factor;
256 | return 0;
257 | }
258 |
259 | int
260 | sft_lookup(const SFT *sft, SFT_UChar codepoint, SFT_Glyph *glyph)
261 | {
262 | return glyph_id(sft->font, codepoint, glyph);
263 | }
264 |
265 | int
266 | sft_gmetrics(const SFT *sft, SFT_Glyph glyph, SFT_GMetrics *metrics)
267 | {
268 | int adv, lsb;
269 | double xScale = sft->xScale / sft->font->unitsPerEm;
270 | uint_fast32_t outline;
271 | int bbox[4];
272 |
273 | memset(metrics, 0, sizeof *metrics);
274 |
275 | if (hor_metrics(sft->font, glyph, &adv, &lsb) < 0)
276 | return -1;
277 | metrics->advanceWidth = adv * xScale;
278 | metrics->leftSideBearing = lsb * xScale + sft->xOffset;
279 |
280 | if (outline_offset(sft->font, glyph, &outline) < 0)
281 | return -1;
282 | if (!outline)
283 | return 0;
284 | if (glyph_bbox(sft, outline, bbox) < 0)
285 | return -1;
286 | metrics->minWidth = bbox[2] - bbox[0] + 1;
287 | metrics->minHeight = bbox[3] - bbox[1] + 1;
288 | metrics->yOffset = sft->flags & SFT_DOWNWARD_Y ? -bbox[3] : bbox[1];
289 |
290 | return 0;
291 | }
292 |
293 | int
294 | sft_kerning(const SFT *sft, SFT_Glyph leftGlyph, SFT_Glyph rightGlyph,
295 | SFT_Kerning *kerning)
296 | {
297 | void *match;
298 | uint_fast32_t offset;
299 | unsigned int numTables, numPairs, length, format, flags;
300 | int value;
301 | uint8_t key[4];
302 |
303 | memset(kerning, 0, sizeof *kerning);
304 |
305 | if (gettable(sft->font, "kern", &offset) < 0)
306 | return 0;
307 |
308 | /* Read kern table header. */
309 | if (!is_safe_offset(sft->font, offset, 4))
310 | return -1;
311 | if (getu16(sft->font, offset) != 0)
312 | return 0;
313 | numTables = getu16(sft->font, offset + 2);
314 | offset += 4;
315 |
316 | while (numTables > 0) {
317 | /* Read subtable header. */
318 | if (!is_safe_offset(sft->font, offset, 6))
319 | return -1;
320 | length = getu16(sft->font, offset + 2);
321 | format = getu8 (sft->font, offset + 4);
322 | flags = getu8 (sft->font, offset + 5);
323 | offset += 6;
324 |
325 | if (format == 0 && (flags & HORIZONTAL_KERNING) && !(flags & MINIMUM_KERNING)) {
326 | /* Read format 0 header. */
327 | if (!is_safe_offset(sft->font, offset, 8))
328 | return -1;
329 | numPairs = getu16(sft->font, offset);
330 | offset += 8;
331 | /* Look up character code pair via binary search. */
332 | key[0] = (leftGlyph >> 8) & 0xFF;
333 | key[1] = leftGlyph & 0xFF;
334 | key[2] = (rightGlyph >> 8) & 0xFF;
335 | key[3] = rightGlyph & 0xFF;
336 | if ((match = bsearch(key, sft->font->memory + offset,
337 | numPairs, 6, cmpu32)) != NULL) {
338 |
339 | value = geti16(sft->font, (uint_fast32_t) ((uint8_t *) match - sft->font->memory + 4));
340 | if (flags & CROSS_STREAM_KERNING) {
341 | kerning->yShift += value;
342 | } else {
343 | kerning->xShift += value;
344 | }
345 | }
346 |
347 | }
348 |
349 | offset += length;
350 | --numTables;
351 | }
352 |
353 | kerning->xShift = kerning->xShift / sft->font->unitsPerEm * sft->xScale;
354 | kerning->yShift = kerning->yShift / sft->font->unitsPerEm * sft->yScale;
355 |
356 | return 0;
357 | }
358 |
359 | int
360 | sft_render(const SFT *sft, SFT_Glyph glyph, SFT_Image image)
361 | {
362 | uint_fast32_t outline;
363 | double transform[6];
364 | int bbox[4];
365 | Outline outl;
366 |
367 | if (outline_offset(sft->font, glyph, &outline) < 0)
368 | return -1;
369 | if (!outline)
370 | return 0;
371 | if (glyph_bbox(sft, outline, bbox) < 0)
372 | return -1;
373 | /* Set up the transformation matrix such that
374 | * the transformed bounding boxes min corner lines
375 | * up with the (0, 0) point. */
376 | transform[0] = sft->xScale / sft->font->unitsPerEm;
377 | transform[1] = 0.0;
378 | transform[2] = 0.0;
379 | transform[4] = sft->xOffset - bbox[0];
380 | if (sft->flags & SFT_DOWNWARD_Y) {
381 | transform[3] = -sft->yScale / sft->font->unitsPerEm;
382 | transform[5] = bbox[3] - sft->yOffset;
383 | } else {
384 | transform[3] = +sft->yScale / sft->font->unitsPerEm;
385 | transform[5] = sft->yOffset - bbox[1];
386 | }
387 |
388 | memset(&outl, 0, sizeof outl);
389 | if (init_outline(&outl) < 0)
390 | goto failure;
391 |
392 | if (decode_outline(sft->font, outline, 0, &outl) < 0)
393 | goto failure;
394 | if (render_outline(&outl, transform, image) < 0)
395 | goto failure;
396 |
397 | free_outline(&outl);
398 | return 0;
399 |
400 | failure:
401 | free_outline(&outl);
402 | return -1;
403 | }
404 |
405 | /* This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX
406 | * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW */
407 | #define MUL_NO_OVERFLOW ((size_t)1 << (sizeof(size_t) * 4))
408 |
409 | /* OpenBSD's reallocarray() standard libary function.
410 | * A wrapper for realloc() that takes two size args like calloc().
411 | * Useful because it eliminates common integer overflow bugs. */
412 | static void *
413 | reallocarray(void *optr, size_t nmemb, size_t size)
414 | {
415 | if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
416 | nmemb > 0 && SIZE_MAX / nmemb < size) {
417 | errno = ENOMEM;
418 | return NULL;
419 | }
420 | return realloc(optr, size * nmemb);
421 | }
422 |
423 | /* TODO maybe we should use long here instead of int. */
424 | static inline int
425 | fast_floor(double x)
426 | {
427 | int i = (int) x;
428 | return i - (i > x);
429 | }
430 |
431 | static inline int
432 | fast_ceil(double x)
433 | {
434 | int i = (int) x;
435 | return i + (i < x);
436 | }
437 |
438 | #if defined(_WIN32)
439 |
440 | static int
441 | map_file(SFT_Font *font, const char *filename)
442 | {
443 | HANDLE file;
444 | DWORD high, low;
445 |
446 | font->mapping = NULL;
447 | font->memory = NULL;
448 |
449 | file = CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
450 | if (file == INVALID_HANDLE_VALUE) {
451 | return -1;
452 | }
453 |
454 | low = GetFileSize(file, &high);
455 | if (low == INVALID_FILE_SIZE) {
456 | CloseHandle(file);
457 | return -1;
458 | }
459 |
460 | font->size = (size_t)high << (8 * sizeof(DWORD)) | low;
461 |
462 | font->mapping = CreateFileMapping(file, NULL, PAGE_READONLY, high, low, NULL);
463 | if (!font->mapping) {
464 | CloseHandle(file);
465 | return -1;
466 | }
467 |
468 | CloseHandle(file);
469 |
470 | font->memory = MapViewOfFile(font->mapping, FILE_MAP_READ, 0, 0, 0);
471 | if (!font->memory) {
472 | CloseHandle(font->mapping);
473 | font->mapping = NULL;
474 | return -1;
475 | }
476 |
477 | return 0;
478 | }
479 |
480 | static void
481 | unmap_file(SFT_Font *font)
482 | {
483 | if (font->memory) {
484 | UnmapViewOfFile(font->memory);
485 | font->memory = NULL;
486 | }
487 | if (font->mapping) {
488 | CloseHandle(font->mapping);
489 | font->mapping = NULL;
490 | }
491 | }
492 |
493 | #else
494 |
495 | static int
496 | map_file(SFT_Font *font, const char *filename)
497 | {
498 | struct stat info;
499 | int fd;
500 | font->memory = MAP_FAILED;
501 | font->size = 0;
502 | font->source = SrcMapping;
503 | if ((fd = open(filename, O_RDONLY)) < 0) {
504 | return -1;
505 | }
506 | if (fstat(fd, &info) < 0) {
507 | close(fd);
508 | return -1;
509 | }
510 | /* FIXME do some basic validation on info.st_size maybe - it is signed for example, so it *could* be negative .. */
511 | font->memory = mmap(NULL, (size_t) info.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
512 | font->size = (uint_fast32_t) info.st_size;
513 | close(fd);
514 | return font->memory == MAP_FAILED ? -1 : 0;
515 | }
516 |
517 | static void
518 | unmap_file(SFT_Font *font)
519 | {
520 | assert(font->memory != MAP_FAILED);
521 | munmap((void *) font->memory, font->size);
522 | }
523 |
524 | #endif
525 |
526 | static int
527 | init_font(SFT_Font *font)
528 | {
529 | uint_fast32_t scalerType, head, hhea;
530 |
531 | if (!is_safe_offset(font, 0, 12))
532 | return -1;
533 | /* Check for a compatible scalerType (magic number). */
534 | scalerType = getu32(font, 0);
535 | if (scalerType != FILE_MAGIC_ONE && scalerType != FILE_MAGIC_TWO)
536 | return -1;
537 |
538 | if (gettable(font, "head", &head) < 0)
539 | return -1;
540 | if (!is_safe_offset(font, head, 54))
541 | return -1;
542 | font->unitsPerEm = getu16(font, head + 18);
543 | font->locaFormat = geti16(font, head + 50);
544 |
545 | if (gettable(font, "hhea", &hhea) < 0)
546 | return -1;
547 | if (!is_safe_offset(font, hhea, 36))
548 | return -1;
549 | font->numLongHmtx = getu16(font, hhea + 34);
550 |
551 | return 0;
552 | }
553 |
554 | static Point
555 | midpoint(Point a, Point b)
556 | {
557 | return (Point) {
558 | 0.5 * (a.x + b.x),
559 | 0.5 * (a.y + b.y)
560 | };
561 | }
562 |
563 | /* Applies an affine linear transformation matrix to a set of points. */
564 | static void
565 | transform_points(unsigned int numPts, Point *points, double trf[6])
566 | {
567 | Point pt;
568 | unsigned int i;
569 | for (i = 0; i < numPts; ++i) {
570 | pt = points[i];
571 | points[i] = (Point) {
572 | pt.x * trf[0] + pt.y * trf[2] + trf[4],
573 | pt.x * trf[1] + pt.y * trf[3] + trf[5]
574 | };
575 | }
576 | }
577 |
578 | static void
579 | clip_points(unsigned int numPts, Point *points, int width, int height)
580 | {
581 | Point pt;
582 | unsigned int i;
583 |
584 | for (i = 0; i < numPts; ++i) {
585 | pt = points[i];
586 |
587 | if (pt.x < 0.0) {
588 | points[i].x = 0.0;
589 | }
590 | if (pt.x >= width) {
591 | points[i].x = nextafter(width, 0.0);
592 | }
593 | if (pt.y < 0.0) {
594 | points[i].y = 0.0;
595 | }
596 | if (pt.y >= height) {
597 | points[i].y = nextafter(height, 0.0);
598 | }
599 | }
600 | }
601 |
602 | static int
603 | init_outline(Outline *outl)
604 | {
605 | /* TODO Smaller initial allocations */
606 | outl->numPoints = 0;
607 | outl->capPoints = 64;
608 | if (!(outl->points = malloc(outl->capPoints * sizeof *outl->points)))
609 | return -1;
610 | outl->numCurves = 0;
611 | outl->capCurves = 64;
612 | if (!(outl->curves = malloc(outl->capCurves * sizeof *outl->curves)))
613 | return -1;
614 | outl->numLines = 0;
615 | outl->capLines = 64;
616 | if (!(outl->lines = malloc(outl->capLines * sizeof *outl->lines)))
617 | return -1;
618 | return 0;
619 | }
620 |
621 | static void
622 | free_outline(Outline *outl)
623 | {
624 | free(outl->points);
625 | free(outl->curves);
626 | free(outl->lines);
627 | }
628 |
629 | static int
630 | grow_points(Outline *outl)
631 | {
632 | void *mem;
633 | uint_fast16_t cap;
634 | assert(outl->capPoints);
635 | /* Since we use uint_fast16_t for capacities, we have to be extra careful not to trigger integer overflow. */
636 | if (outl->capPoints > UINT16_MAX / 2)
637 | return -1;
638 | cap = (uint_fast16_t) (2U * outl->capPoints);
639 | if (!(mem = reallocarray(outl->points, cap, sizeof *outl->points)))
640 | return -1;
641 | outl->capPoints = (uint_least16_t) cap;
642 | outl->points = mem;
643 | return 0;
644 | }
645 |
646 | static int
647 | grow_curves(Outline *outl)
648 | {
649 | void *mem;
650 | uint_fast16_t cap;
651 | assert(outl->capCurves);
652 | if (outl->capCurves > UINT16_MAX / 2)
653 | return -1;
654 | cap = (uint_fast16_t) (2U * outl->capCurves);
655 | if (!(mem = reallocarray(outl->curves, cap, sizeof *outl->curves)))
656 | return -1;
657 | outl->capCurves = (uint_least16_t) cap;
658 | outl->curves = mem;
659 | return 0;
660 | }
661 |
662 | static int
663 | grow_lines(Outline *outl)
664 | {
665 | void *mem;
666 | uint_fast16_t cap;
667 | assert(outl->capLines);
668 | if (outl->capLines > UINT16_MAX / 2)
669 | return -1;
670 | cap = (uint_fast16_t) (2U * outl->capLines);
671 | if (!(mem = reallocarray(outl->lines, cap, sizeof *outl->lines)))
672 | return -1;
673 | outl->capLines = (uint_least16_t) cap;
674 | outl->lines = mem;
675 | return 0;
676 | }
677 |
678 | static inline int
679 | is_safe_offset(SFT_Font *font, uint_fast32_t offset, uint_fast32_t margin)
680 | {
681 | if (offset > font->size) return 0;
682 | if (font->size - offset < margin) return 0;
683 | return 1;
684 | }
685 |
686 | /* Like bsearch(), but returns the next highest element if key could not be found. */
687 | static void *
688 | csearch(const void *key, const void *base,
689 | size_t nmemb, size_t size,
690 | int (*compar)(const void *, const void *))
691 | {
692 | const uint8_t *bytes = base, *sample;
693 | size_t low = 0, high = nmemb - 1, mid;
694 | if (!nmemb) return NULL;
695 | while (low != high) {
696 | mid = low + (high - low) / 2;
697 | sample = bytes + mid * size;
698 | if (compar(key, sample) > 0) {
699 | low = mid + 1;
700 | } else {
701 | high = mid;
702 | }
703 | }
704 | return (uint8_t *) bytes + low * size;
705 | }
706 |
707 | /* Used as a comparison function for [bc]search(). */
708 | static int
709 | cmpu16(const void *a, const void *b)
710 | {
711 | return memcmp(a, b, 2);
712 | }
713 |
714 | /* Used as a comparison function for [bc]search(). */
715 | static int
716 | cmpu32(const void *a, const void *b)
717 | {
718 | return memcmp(a, b, 4);
719 | }
720 |
721 | static inline uint_least8_t
722 | getu8(SFT_Font *font, uint_fast32_t offset)
723 | {
724 | assert(offset + 1 <= font->size);
725 | return *(font->memory + offset);
726 | }
727 |
728 | static inline int_least8_t
729 | geti8(SFT_Font *font, uint_fast32_t offset)
730 | {
731 | return (int_least8_t) getu8(font, offset);
732 | }
733 |
734 | static inline uint_least16_t
735 | getu16(SFT_Font *font, uint_fast32_t offset)
736 | {
737 | assert(offset + 2 <= font->size);
738 | const uint8_t *base = font->memory + offset;
739 | uint_least16_t b1 = base[0], b0 = base[1];
740 | return (uint_least16_t) (b1 << 8 | b0);
741 | }
742 |
743 | static inline int16_t
744 | geti16(SFT_Font *font, uint_fast32_t offset)
745 | {
746 | return (int_least16_t) getu16(font, offset);
747 | }
748 |
749 | static inline uint32_t
750 | getu32(SFT_Font *font, uint_fast32_t offset)
751 | {
752 | assert(offset + 4 <= font->size);
753 | const uint8_t *base = font->memory + offset;
754 | uint_least32_t b3 = base[0], b2 = base[1], b1 = base[2], b0 = base[3];
755 | return (uint_least32_t) (b3 << 24 | b2 << 16 | b1 << 8 | b0);
756 | }
757 |
758 | static int
759 | gettable(SFT_Font *font, char tag[4], uint_fast32_t *offset)
760 | {
761 | void *match;
762 | unsigned int numTables;
763 | /* No need to bounds-check access to the first 12 bytes - this gets already checked by init_font(). */
764 | numTables = getu16(font, 4);
765 | if (!is_safe_offset(font, 12, (uint_fast32_t) numTables * 16))
766 | return -1;
767 | if (!(match = bsearch(tag, font->memory + 12, numTables, 16, cmpu32)))
768 | return -1;
769 | *offset = getu32(font, (uint_fast32_t) ((uint8_t *) match - font->memory + 8));
770 | return 0;
771 | }
772 |
773 | static int
774 | cmap_fmt4(SFT_Font *font, uint_fast32_t table, SFT_UChar charCode, SFT_Glyph *glyph)
775 | {
776 | const uint8_t *segPtr;
777 | uint_fast32_t segIdxX2;
778 | uint_fast32_t endCodes, startCodes, idDeltas, idRangeOffsets, idOffset;
779 | uint_fast16_t segCountX2, idRangeOffset, startCode, shortCode, idDelta, id;
780 | uint8_t key[2] = { (uint8_t) (charCode >> 8), (uint8_t) charCode };
781 | /* cmap format 4 only supports the Unicode BMP. */
782 | if (charCode > 0xFFFF) {
783 | *glyph = 0;
784 | return 0;
785 | }
786 | shortCode = (uint_fast16_t) charCode;
787 | if (!is_safe_offset(font, table, 8))
788 | return -1;
789 | segCountX2 = getu16(font, table);
790 | if ((segCountX2 & 1) || !segCountX2)
791 | return -1;
792 | /* Find starting positions of the relevant arrays. */
793 | endCodes = table + 8;
794 | startCodes = endCodes + segCountX2 + 2;
795 | idDeltas = startCodes + segCountX2;
796 | idRangeOffsets = idDeltas + segCountX2;
797 | if (!is_safe_offset(font, idRangeOffsets, segCountX2))
798 | return -1;
799 | /* Find the segment that contains shortCode by binary searching over
800 | * the highest codes in the segments. */
801 | segPtr = csearch(key, font->memory + endCodes, segCountX2 / 2, 2, cmpu16);
802 | segIdxX2 = (uint_fast32_t) (segPtr - (font->memory + endCodes));
803 | /* Look up segment info from the arrays & short circuit if the spec requires. */
804 | if ((startCode = getu16(font, startCodes + segIdxX2)) > shortCode)
805 | return 0;
806 | idDelta = getu16(font, idDeltas + segIdxX2);
807 | if (!(idRangeOffset = getu16(font, idRangeOffsets + segIdxX2))) {
808 | /* Intentional integer under- and overflow. */
809 | *glyph = (shortCode + idDelta) & 0xFFFF;
810 | return 0;
811 | }
812 | /* Calculate offset into glyph array and determine ultimate value. */
813 | idOffset = idRangeOffsets + segIdxX2 + idRangeOffset + 2U * (unsigned int) (shortCode - startCode);
814 | if (!is_safe_offset(font, idOffset, 2))
815 | return -1;
816 | id = getu16(font, idOffset);
817 | /* Intentional integer under- and overflow. */
818 | *glyph = id ? (id + idDelta) & 0xFFFF : 0;
819 | return 0;
820 | }
821 |
822 | static int
823 | cmap_fmt6(SFT_Font *font, uint_fast32_t table, SFT_UChar charCode, SFT_Glyph *glyph)
824 | {
825 | unsigned int firstCode, entryCount;
826 | /* cmap format 6 only supports the Unicode BMP. */
827 | if (charCode > 0xFFFF) {
828 | *glyph = 0;
829 | return 0;
830 | }
831 | if (!is_safe_offset(font, table, 4))
832 | return -1;
833 | firstCode = getu16(font, table);
834 | entryCount = getu16(font, table + 2);
835 | if (!is_safe_offset(font, table, 4 + 2 * entryCount))
836 | return -1;
837 | if (charCode < firstCode)
838 | return -1;
839 | charCode -= firstCode;
840 | if (!(charCode < entryCount))
841 | return -1;
842 | *glyph = getu16(font, table + 4 + 2 * charCode);
843 | return 0;
844 | }
845 |
846 | static int
847 | cmap_fmt12_13(SFT_Font *font, uint_fast32_t table, SFT_UChar charCode, SFT_Glyph *glyph, int which)
848 | {
849 | uint32_t len, numEntries;
850 | uint_fast32_t i;
851 |
852 | *glyph = 0;
853 |
854 | /* check that the entire header is present */
855 | if (!is_safe_offset(font, table, 16))
856 | return -1;
857 |
858 | len = getu32(font, table + 4);
859 |
860 | /* A minimal header is 16 bytes */
861 | if (len < 16)
862 | return -1;
863 |
864 | if (!is_safe_offset(font, table, len))
865 | return -1;
866 |
867 | numEntries = getu32(font, table + 12);
868 |
869 | for (i = 0; i < numEntries; ++i) {
870 | uint32_t firstCode, lastCode, glyphOffset;
871 | firstCode = getu32(font, table + (i * 12) + 16);
872 | lastCode = getu32(font, table + (i * 12) + 16 + 4);
873 | if (charCode < firstCode || charCode > lastCode)
874 | continue;
875 | glyphOffset = getu32(font, table + (i * 12) + 16 + 8);
876 | if (which == 12)
877 | *glyph = (charCode-firstCode) + glyphOffset;
878 | else
879 | *glyph = glyphOffset;
880 | return 0;
881 | }
882 |
883 | return 0;
884 | }
885 |
886 | /* Maps Unicode code points to glyph indices. */
887 | static int
888 | glyph_id(SFT_Font *font, SFT_UChar charCode, SFT_Glyph *glyph)
889 | {
890 | uint_fast32_t cmap, entry, table;
891 | unsigned int idx, numEntries;
892 | int type, format;
893 |
894 | *glyph = 0;
895 |
896 | if (gettable(font, "cmap", &cmap) < 0)
897 | return -1;
898 |
899 | if (!is_safe_offset(font, cmap, 4))
900 | return -1;
901 | numEntries = getu16(font, cmap + 2);
902 |
903 | if (!is_safe_offset(font, cmap, 4 + numEntries * 8))
904 | return -1;
905 |
906 | /* First look for a 'full repertoire'/non-BMP map. */
907 | for (idx = 0; idx < numEntries; ++idx) {
908 | entry = cmap + 4 + idx * 8;
909 | type = getu16(font, entry) * 0100 + getu16(font, entry + 2);
910 | /* Complete unicode map */
911 | if (type == 0004 || type == 0312) {
912 | table = cmap + getu32(font, entry + 4);
913 | if (!is_safe_offset(font, table, 8))
914 | return -1;
915 | /* Dispatch based on cmap format. */
916 | format = getu16(font, table);
917 | switch (format) {
918 | case 12:
919 | return cmap_fmt12_13(font, table, charCode, glyph, 12);
920 | default:
921 | return -1;
922 | }
923 | }
924 | }
925 |
926 | /* If no 'full repertoire' cmap was found, try looking for a BMP map. */
927 | for (idx = 0; idx < numEntries; ++idx) {
928 | entry = cmap + 4 + idx * 8;
929 | type = getu16(font, entry) * 0100 + getu16(font, entry + 2);
930 | /* Unicode BMP */
931 | if (type == 0003 || type == 0301) {
932 | table = cmap + getu32(font, entry + 4);
933 | if (!is_safe_offset(font, table, 6))
934 | return -1;
935 | /* Dispatch based on cmap format. */
936 | switch (getu16(font, table)) {
937 | case 4:
938 | return cmap_fmt4(font, table + 6, charCode, glyph);
939 | case 6:
940 | return cmap_fmt6(font, table + 6, charCode, glyph);
941 | default:
942 | return -1;
943 | }
944 | }
945 | }
946 |
947 | return -1;
948 | }
949 |
950 | static int
951 | hor_metrics(SFT_Font *font, SFT_Glyph glyph, int *advanceWidth, int *leftSideBearing)
952 | {
953 | uint_fast32_t hmtx, offset, boundary;
954 | if (gettable(font, "hmtx", &hmtx) < 0)
955 | return -1;
956 | if (glyph < font->numLongHmtx) {
957 | /* glyph is inside long metrics segment. */
958 | offset = hmtx + 4 * glyph;
959 | if (!is_safe_offset(font, offset, 4))
960 | return -1;
961 | *advanceWidth = getu16(font, offset);
962 | *leftSideBearing = geti16(font, offset + 2);
963 | return 0;
964 | } else {
965 | /* glyph is inside short metrics segment. */
966 | boundary = hmtx + 4U * (uint_fast32_t) font->numLongHmtx;
967 | if (boundary < 4)
968 | return -1;
969 |
970 | offset = boundary - 4;
971 | if (!is_safe_offset(font, offset, 4))
972 | return -1;
973 | *advanceWidth = getu16(font, offset);
974 |
975 | offset = boundary + 2 * (glyph - font->numLongHmtx);
976 | if (!is_safe_offset(font, offset, 2))
977 | return -1;
978 | *leftSideBearing = geti16(font, offset);
979 | return 0;
980 | }
981 | }
982 |
983 | static int
984 | glyph_bbox(const SFT *sft, uint_fast32_t outline, int box[4])
985 | {
986 | double xScale, yScale;
987 | /* Read the bounding box from the font file verbatim. */
988 | if (!is_safe_offset(sft->font, outline, 10))
989 | return -1;
990 | box[0] = geti16(sft->font, outline + 2);
991 | box[1] = geti16(sft->font, outline + 4);
992 | box[2] = geti16(sft->font, outline + 6);
993 | box[3] = geti16(sft->font, outline + 8);
994 | if (box[2] <= box[0] || box[3] <= box[1])
995 | return -1;
996 | /* Transform the bounding box into SFT coordinate space. */
997 | xScale = sft->xScale / sft->font->unitsPerEm;
998 | yScale = sft->yScale / sft->font->unitsPerEm;
999 | box[0] = (int) floor(box[0] * xScale + sft->xOffset);
1000 | box[1] = (int) floor(box[1] * yScale + sft->yOffset);
1001 | box[2] = (int) ceil (box[2] * xScale + sft->xOffset);
1002 | box[3] = (int) ceil (box[3] * yScale + sft->yOffset);
1003 | return 0;
1004 | }
1005 |
1006 | /* Returns the offset into the font that the glyph's outline is stored at. */
1007 | static int
1008 | outline_offset(SFT_Font *font, SFT_Glyph glyph, uint_fast32_t *offset)
1009 | {
1010 | uint_fast32_t loca, glyf;
1011 | uint_fast32_t base, this, next;
1012 |
1013 | if (gettable(font, "loca", &loca) < 0)
1014 | return -1;
1015 | if (gettable(font, "glyf", &glyf) < 0)
1016 | return -1;
1017 |
1018 | if (font->locaFormat == 0) {
1019 | base = loca + 2 * glyph;
1020 |
1021 | if (!is_safe_offset(font, base, 4))
1022 | return -1;
1023 |
1024 | this = 2U * (uint_fast32_t) getu16(font, base);
1025 | next = 2U * (uint_fast32_t) getu16(font, base + 2);
1026 | } else {
1027 | base = loca + 4 * glyph;
1028 |
1029 | if (!is_safe_offset(font, base, 8))
1030 | return -1;
1031 |
1032 | this = getu32(font, base);
1033 | next = getu32(font, base + 4);
1034 | }
1035 |
1036 | *offset = this == next ? 0 : glyf + this;
1037 | return 0;
1038 | }
1039 |
1040 | /* For a 'simple' outline, determines each point of the outline with a set of flags. */
1041 | static int
1042 | simple_flags(SFT_Font *font, uint_fast32_t *offset, uint_fast16_t numPts, uint8_t *flags)
1043 | {
1044 | uint_fast32_t off = *offset;
1045 | uint_fast16_t i;
1046 | uint8_t value = 0, repeat = 0;
1047 | for (i = 0; i < numPts; ++i) {
1048 | if (repeat) {
1049 | --repeat;
1050 | } else {
1051 | if (!is_safe_offset(font, off, 1))
1052 | return -1;
1053 | value = getu8(font, off++);
1054 | if (value & REPEAT_FLAG) {
1055 | if (!is_safe_offset(font, off, 1))
1056 | return -1;
1057 | repeat = getu8(font, off++);
1058 | }
1059 | }
1060 | flags[i] = value;
1061 | }
1062 | *offset = off;
1063 | return 0;
1064 | }
1065 |
1066 | /* For a 'simple' outline, decodes both X and Y coordinates for each point of the outline. */
1067 | static int
1068 | simple_points(SFT_Font *font, uint_fast32_t offset, uint_fast16_t numPts, uint8_t *flags, Point *points)
1069 | {
1070 | long accum, value, bit;
1071 | uint_fast16_t i;
1072 |
1073 | accum = 0L;
1074 | for (i = 0; i < numPts; ++i) {
1075 | if (flags[i] & X_CHANGE_IS_SMALL) {
1076 | if (!is_safe_offset(font, offset, 1))
1077 | return -1;
1078 | value = (long) getu8(font, offset++);
1079 | bit = !!(flags[i] & X_CHANGE_IS_POSITIVE);
1080 | accum -= (value ^ -bit) + bit;
1081 | } else if (!(flags[i] & X_CHANGE_IS_ZERO)) {
1082 | if (!is_safe_offset(font, offset, 2))
1083 | return -1;
1084 | accum += geti16(font, offset);
1085 | offset += 2;
1086 | }
1087 | points[i].x = (double) accum;
1088 | }
1089 |
1090 | accum = 0L;
1091 | for (i = 0; i < numPts; ++i) {
1092 | if (flags[i] & Y_CHANGE_IS_SMALL) {
1093 | if (!is_safe_offset(font, offset, 1))
1094 | return -1;
1095 | value = (long) getu8(font, offset++);
1096 | bit = !!(flags[i] & Y_CHANGE_IS_POSITIVE);
1097 | accum -= (value ^ -bit) + bit;
1098 | } else if (!(flags[i] & Y_CHANGE_IS_ZERO)) {
1099 | if (!is_safe_offset(font, offset, 2))
1100 | return -1;
1101 | accum += geti16(font, offset);
1102 | offset += 2;
1103 | }
1104 | points[i].y = (double) accum;
1105 | }
1106 |
1107 | return 0;
1108 | }
1109 |
1110 | static int
1111 | decode_contour(uint8_t *flags, uint_fast16_t basePoint, uint_fast16_t count, Outline *outl)
1112 | {
1113 | uint_fast16_t i;
1114 | uint_least16_t looseEnd, beg, ctrl, center, cur;
1115 | unsigned int gotCtrl;
1116 |
1117 | /* Skip contours with less than two points, since the following algorithm can't handle them and
1118 | * they should appear invisible either way (because they don't have any area). */
1119 | if (count < 2) return 0;
1120 |
1121 | assert(basePoint <= UINT16_MAX - count);
1122 |
1123 | if (flags[0] & POINT_IS_ON_CURVE) {
1124 | looseEnd = (uint_least16_t) basePoint++;
1125 | ++flags;
1126 | --count;
1127 | } else if (flags[count - 1] & POINT_IS_ON_CURVE) {
1128 | looseEnd = (uint_least16_t) (basePoint + --count);
1129 | } else {
1130 | if (outl->numPoints >= outl->capPoints && grow_points(outl) < 0)
1131 | return -1;
1132 |
1133 | looseEnd = outl->numPoints;
1134 | outl->points[outl->numPoints++] = midpoint(
1135 | outl->points[basePoint],
1136 | outl->points[basePoint + count - 1]);
1137 | }
1138 | beg = looseEnd;
1139 | gotCtrl = 0;
1140 | for (i = 0; i < count; ++i) {
1141 | /* cur can't overflow because we ensure that basePoint + count < 0xFFFF before calling decode_contour(). */
1142 | cur = (uint_least16_t) (basePoint + i);
1143 | /* NOTE clang-analyzer will often flag this and another piece of code because it thinks that flags and
1144 | * outl->points + basePoint don't always get properly initialized -- even when you explicitly loop over both
1145 | * and set every element to zero (but not when you use memset). This is a known clang-analyzer bug:
1146 | * http://clang-developers.42468.n3.nabble.com/StaticAnalyzer-False-positive-with-loop-handling-td4053875.html */
1147 | if (flags[i] & POINT_IS_ON_CURVE) {
1148 | if (gotCtrl) {
1149 | if (outl->numCurves >= outl->capCurves && grow_curves(outl) < 0)
1150 | return -1;
1151 | outl->curves[outl->numCurves++] = (Curve) { beg, cur, ctrl };
1152 | } else {
1153 | if (outl->numLines >= outl->capLines && grow_lines(outl) < 0)
1154 | return -1;
1155 | outl->lines[outl->numLines++] = (Line) { beg, cur };
1156 | }
1157 | beg = cur;
1158 | gotCtrl = 0;
1159 | } else {
1160 | if (gotCtrl) {
1161 | center = outl->numPoints;
1162 | if (outl->numPoints >= outl->capPoints && grow_points(outl) < 0)
1163 | return -1;
1164 | outl->points[center] = midpoint(outl->points[ctrl], outl->points[cur]);
1165 | ++outl->numPoints;
1166 |
1167 | if (outl->numCurves >= outl->capCurves && grow_curves(outl) < 0)
1168 | return -1;
1169 | outl->curves[outl->numCurves++] = (Curve) { beg, center, ctrl };
1170 |
1171 | beg = center;
1172 | }
1173 | ctrl = cur;
1174 | gotCtrl = 1;
1175 | }
1176 | }
1177 | if (gotCtrl) {
1178 | if (outl->numCurves >= outl->capCurves && grow_curves(outl) < 0)
1179 | return -1;
1180 | outl->curves[outl->numCurves++] = (Curve) { beg, looseEnd, ctrl };
1181 | } else {
1182 | if (outl->numLines >= outl->capLines && grow_lines(outl) < 0)
1183 | return -1;
1184 | outl->lines[outl->numLines++] = (Line) { beg, looseEnd };
1185 | }
1186 |
1187 | return 0;
1188 | }
1189 |
1190 | static int
1191 | simple_outline(SFT_Font *font, uint_fast32_t offset, unsigned int numContours, Outline *outl)
1192 | {
1193 | uint_fast16_t *endPts = NULL;
1194 | uint8_t *flags = NULL;
1195 | uint_fast16_t numPts;
1196 | unsigned int i;
1197 |
1198 | assert(numContours > 0);
1199 |
1200 | uint_fast16_t basePoint = outl->numPoints;
1201 |
1202 | if (!is_safe_offset(font, offset, numContours * 2 + 2))
1203 | goto failure;
1204 | numPts = getu16(font, offset + (numContours - 1) * 2);
1205 | if (numPts >= UINT16_MAX)
1206 | goto failure;
1207 | numPts++;
1208 | if (outl->numPoints > UINT16_MAX - numPts)
1209 | goto failure;
1210 |
1211 | while (outl->capPoints < basePoint + numPts) {
1212 | if (grow_points(outl) < 0)
1213 | goto failure;
1214 | }
1215 |
1216 | STACK_ALLOC(endPts, uint_fast16_t, 16, numContours);
1217 | if (endPts == NULL)
1218 | goto failure;
1219 | STACK_ALLOC(flags, uint8_t, 128, numPts);
1220 | if (flags == NULL)
1221 | goto failure;
1222 |
1223 | for (i = 0; i < numContours; ++i) {
1224 | endPts[i] = getu16(font, offset);
1225 | offset += 2;
1226 | }
1227 | /* Ensure that endPts are never falling.
1228 | * Falling endPts have no sensible interpretation and most likely only occur in malicious input.
1229 | * Therefore, we bail, should we ever encounter such input. */
1230 | for (i = 0; i < numContours - 1; ++i) {
1231 | if (endPts[i + 1] < endPts[i] + 1)
1232 | goto failure;
1233 | }
1234 | offset += 2U + getu16(font, offset);
1235 |
1236 | if (simple_flags(font, &offset, numPts, flags) < 0)
1237 | goto failure;
1238 | if (simple_points(font, offset, numPts, flags, outl->points + basePoint) < 0)
1239 | goto failure;
1240 | outl->numPoints = (uint_least16_t) (outl->numPoints + numPts);
1241 |
1242 | uint_fast16_t beg = 0;
1243 | for (i = 0; i < numContours; ++i) {
1244 | uint_fast16_t count = endPts[i] - beg + 1;
1245 | if (decode_contour(flags + beg, basePoint + beg, count, outl) < 0)
1246 | goto failure;
1247 | beg = endPts[i] + 1;
1248 | }
1249 |
1250 | STACK_FREE(endPts);
1251 | STACK_FREE(flags);
1252 | return 0;
1253 | failure:
1254 | STACK_FREE(endPts);
1255 | STACK_FREE(flags);
1256 | return -1;
1257 | }
1258 |
1259 | static int
1260 | compound_outline(SFT_Font *font, uint_fast32_t offset, int recDepth, Outline *outl)
1261 | {
1262 | double local[6];
1263 | uint_fast32_t outline;
1264 | unsigned int flags, glyph, basePoint;
1265 | /* Guard against infinite recursion (compound glyphs that have themselves as component). */
1266 | if (recDepth >= 4)
1267 | return -1;
1268 | do {
1269 | memset(local, 0, sizeof local);
1270 | if (!is_safe_offset(font, offset, 4))
1271 | return -1;
1272 | flags = getu16(font, offset);
1273 | glyph = getu16(font, offset + 2);
1274 | offset += 4;
1275 | /* We don't implement point matching, and neither does stb_truetype for that matter. */
1276 | if (!(flags & ACTUAL_XY_OFFSETS))
1277 | return -1;
1278 | /* Read additional X and Y offsets (in FUnits) of this component. */
1279 | if (flags & OFFSETS_ARE_LARGE) {
1280 | if (!is_safe_offset(font, offset, 4))
1281 | return -1;
1282 | local[4] = geti16(font, offset);
1283 | local[5] = geti16(font, offset + 2);
1284 | offset += 4;
1285 | } else {
1286 | if (!is_safe_offset(font, offset, 2))
1287 | return -1;
1288 | local[4] = geti8(font, offset);
1289 | local[5] = geti8(font, offset + 1);
1290 | offset += 2;
1291 | }
1292 | if (flags & GOT_A_SINGLE_SCALE) {
1293 | if (!is_safe_offset(font, offset, 2))
1294 | return -1;
1295 | local[0] = geti16(font, offset) / 16384.0;
1296 | local[3] = local[0];
1297 | offset += 2;
1298 | } else if (flags & GOT_AN_X_AND_Y_SCALE) {
1299 | if (!is_safe_offset(font, offset, 4))
1300 | return -1;
1301 | local[0] = geti16(font, offset + 0) / 16384.0;
1302 | local[3] = geti16(font, offset + 2) / 16384.0;
1303 | offset += 4;
1304 | } else if (flags & GOT_A_SCALE_MATRIX) {
1305 | if (!is_safe_offset(font, offset, 8))
1306 | return -1;
1307 | local[0] = geti16(font, offset + 0) / 16384.0;
1308 | local[1] = geti16(font, offset + 2) / 16384.0;
1309 | local[2] = geti16(font, offset + 4) / 16384.0;
1310 | local[3] = geti16(font, offset + 6) / 16384.0;
1311 | offset += 8;
1312 | } else {
1313 | local[0] = 1.0;
1314 | local[3] = 1.0;
1315 | }
1316 | /* At this point, Apple's spec more or less tells you to scale the matrix by its own L1 norm.
1317 | * But stb_truetype scales by the L2 norm. And FreeType2 doesn't scale at all.
1318 | * Furthermore, Microsoft's spec doesn't even mention anything like this.
1319 | * It's almost as if nobody ever uses this feature anyway. */
1320 | if (outline_offset(font, glyph, &outline) < 0)
1321 | return -1;
1322 | if (outline) {
1323 | basePoint = outl->numPoints;
1324 | if (decode_outline(font, outline, recDepth + 1, outl) < 0)
1325 | return -1;
1326 | transform_points(outl->numPoints - basePoint, outl->points + basePoint, local);
1327 | }
1328 | } while (flags & THERE_ARE_MORE_COMPONENTS);
1329 |
1330 | return 0;
1331 | }
1332 |
1333 | static int
1334 | decode_outline(SFT_Font *font, uint_fast32_t offset, int recDepth, Outline *outl)
1335 | {
1336 | int numContours;
1337 | if (!is_safe_offset(font, offset, 10))
1338 | return -1;
1339 | numContours = geti16(font, offset);
1340 | if (numContours > 0) {
1341 | /* Glyph has a 'simple' outline consisting of a number of contours. */
1342 | return simple_outline(font, offset + 10, (unsigned int) numContours, outl);
1343 | } else if (numContours < 0) {
1344 | /* Glyph has a compound outline combined from mutiple other outlines. */
1345 | return compound_outline(font, offset + 10, recDepth, outl);
1346 | } else {
1347 | return 0;
1348 | }
1349 | }
1350 |
1351 | /* A heuristic to tell whether a given curve can be approximated closely enough by a line. */
1352 | static int
1353 | is_flat(Outline *outl, Curve curve)
1354 | {
1355 | const double maxArea2 = 2.0;
1356 | Point a = outl->points[curve.beg];
1357 | Point b = outl->points[curve.ctrl];
1358 | Point c = outl->points[curve.end];
1359 | Point g = { b.x-a.x, b.y-a.y };
1360 | Point h = { c.x-a.x, c.y-a.y };
1361 | double area2 = fabs(g.x*h.y-h.x*g.y);
1362 | return area2 <= maxArea2;
1363 | }
1364 |
1365 | static int
1366 | tesselate_curve(Curve curve, Outline *outl)
1367 | {
1368 | /* From my tests I can conclude that this stack barely reaches a top height
1369 | * of 4 elements even for the largest font sizes I'm willing to support. And
1370 | * as space requirements should only grow logarithmically, I think 10 is
1371 | * more than enough. */
1372 | #define STACK_SIZE 10
1373 | Curve stack[STACK_SIZE];
1374 | unsigned int top = 0;
1375 | for (;;) {
1376 | if (is_flat(outl, curve) || top >= STACK_SIZE) {
1377 | if (outl->numLines >= outl->capLines && grow_lines(outl) < 0)
1378 | return -1;
1379 | outl->lines[outl->numLines++] = (Line) { curve.beg, curve.end };
1380 | if (top == 0) break;
1381 | curve = stack[--top];
1382 | } else {
1383 | uint_least16_t ctrl0 = outl->numPoints;
1384 | if (outl->numPoints >= outl->capPoints && grow_points(outl) < 0)
1385 | return -1;
1386 | outl->points[ctrl0] = midpoint(outl->points[curve.beg], outl->points[curve.ctrl]);
1387 | ++outl->numPoints;
1388 |
1389 | uint_least16_t ctrl1 = outl->numPoints;
1390 | if (outl->numPoints >= outl->capPoints && grow_points(outl) < 0)
1391 | return -1;
1392 | outl->points[ctrl1] = midpoint(outl->points[curve.ctrl], outl->points[curve.end]);
1393 | ++outl->numPoints;
1394 |
1395 | uint_least16_t pivot = outl->numPoints;
1396 | if (outl->numPoints >= outl->capPoints && grow_points(outl) < 0)
1397 | return -1;
1398 | outl->points[pivot] = midpoint(outl->points[ctrl0], outl->points[ctrl1]);
1399 | ++outl->numPoints;
1400 |
1401 | stack[top++] = (Curve) { curve.beg, pivot, ctrl0 };
1402 | curve = (Curve) { pivot, curve.end, ctrl1 };
1403 | }
1404 | }
1405 | return 0;
1406 | #undef STACK_SIZE
1407 | }
1408 |
1409 | static int
1410 | tesselate_curves(Outline *outl)
1411 | {
1412 | unsigned int i;
1413 | for (i = 0; i < outl->numCurves; ++i) {
1414 | if (tesselate_curve(outl->curves[i], outl) < 0)
1415 | return -1;
1416 | }
1417 | return 0;
1418 | }
1419 |
1420 | /* Draws a line into the buffer. Uses a custom 2D raycasting algorithm to do so. */
1421 | static void
1422 | draw_line(Raster buf, Point origin, Point goal)
1423 | {
1424 | Point delta;
1425 | Point nextCrossing;
1426 | Point crossingIncr;
1427 | double halfDeltaX;
1428 | double prevDistance = 0.0, nextDistance;
1429 | double xAverage, yDifference;
1430 | struct { int x, y; } pixel;
1431 | struct { int x, y; } dir;
1432 | int step, numSteps = 0;
1433 | Cell *restrict cptr, cell;
1434 |
1435 | delta.x = goal.x - origin.x;
1436 | delta.y = goal.y - origin.y;
1437 | dir.x = SIGN(delta.x);
1438 | dir.y = SIGN(delta.y);
1439 |
1440 | if (!dir.y) {
1441 | return;
1442 | }
1443 |
1444 | crossingIncr.x = dir.x ? fabs(1.0 / delta.x) : 1.0;
1445 | crossingIncr.y = fabs(1.0 / delta.y);
1446 |
1447 | if (!dir.x) {
1448 | pixel.x = fast_floor(origin.x);
1449 | nextCrossing.x = 100.0;
1450 | } else {
1451 | if (dir.x > 0) {
1452 | pixel.x = fast_floor(origin.x);
1453 | nextCrossing.x = (origin.x - pixel.x) * crossingIncr.x;
1454 | nextCrossing.x = crossingIncr.x - nextCrossing.x;
1455 | numSteps += fast_ceil(goal.x) - fast_floor(origin.x) - 1;
1456 | } else {
1457 | pixel.x = fast_ceil(origin.x) - 1;
1458 | nextCrossing.x = (origin.x - pixel.x) * crossingIncr.x;
1459 | numSteps += fast_ceil(origin.x) - fast_floor(goal.x) - 1;
1460 | }
1461 | }
1462 |
1463 | if (dir.y > 0) {
1464 | pixel.y = fast_floor(origin.y);
1465 | nextCrossing.y = (origin.y - pixel.y) * crossingIncr.y;
1466 | nextCrossing.y = crossingIncr.y - nextCrossing.y;
1467 | numSteps += fast_ceil(goal.y) - fast_floor(origin.y) - 1;
1468 | } else {
1469 | pixel.y = fast_ceil(origin.y) - 1;
1470 | nextCrossing.y = (origin.y - pixel.y) * crossingIncr.y;
1471 | numSteps += fast_ceil(origin.y) - fast_floor(goal.y) - 1;
1472 | }
1473 |
1474 | nextDistance = MIN(nextCrossing.x, nextCrossing.y);
1475 | halfDeltaX = 0.5 * delta.x;
1476 |
1477 | for (step = 0; step < numSteps; ++step) {
1478 | xAverage = origin.x + (prevDistance + nextDistance) * halfDeltaX;
1479 | yDifference = (nextDistance - prevDistance) * delta.y;
1480 | cptr = &buf.cells[pixel.y * buf.width + pixel.x];
1481 | cell = *cptr;
1482 | cell.cover += yDifference;
1483 | xAverage -= (double) pixel.x;
1484 | cell.area += (1.0 - xAverage) * yDifference;
1485 | *cptr = cell;
1486 | prevDistance = nextDistance;
1487 | int alongX = nextCrossing.x < nextCrossing.y;
1488 | pixel.x += alongX ? dir.x : 0;
1489 | pixel.y += alongX ? 0 : dir.y;
1490 | nextCrossing.x += alongX ? crossingIncr.x : 0.0;
1491 | nextCrossing.y += alongX ? 0.0 : crossingIncr.y;
1492 | nextDistance = MIN(nextCrossing.x, nextCrossing.y);
1493 | }
1494 |
1495 | xAverage = origin.x + (prevDistance + 1.0) * halfDeltaX;
1496 | yDifference = (1.0 - prevDistance) * delta.y;
1497 | cptr = &buf.cells[pixel.y * buf.width + pixel.x];
1498 | cell = *cptr;
1499 | cell.cover += yDifference;
1500 | xAverage -= (double) pixel.x;
1501 | cell.area += (1.0 - xAverage) * yDifference;
1502 | *cptr = cell;
1503 | }
1504 |
1505 | static void
1506 | draw_lines(Outline *outl, Raster buf)
1507 | {
1508 | unsigned int i;
1509 | for (i = 0; i < outl->numLines; ++i) {
1510 | Line line = outl->lines[i];
1511 | Point origin = outl->points[line.beg];
1512 | Point goal = outl->points[line.end];
1513 | draw_line(buf, origin, goal);
1514 | }
1515 | }
1516 |
1517 | /* Integrate the values in the buffer to arrive at the final grayscale image. */
1518 | static void
1519 | post_process(Raster buf, uint8_t *image)
1520 | {
1521 | Cell cell;
1522 | double accum = 0.0, value;
1523 | unsigned int i, num;
1524 | num = (unsigned int) buf.width * (unsigned int) buf.height;
1525 | for (i = 0; i < num; ++i) {
1526 | cell = buf.cells[i];
1527 | value = fabs(accum + cell.area);
1528 | value = MIN(value, 1.0);
1529 | value = value * 255.0 + 0.5;
1530 | image[i] = (uint8_t) value;
1531 | accum += cell.cover;
1532 | }
1533 | }
1534 |
1535 | static int
1536 | render_outline(Outline *outl, double transform[6], SFT_Image image)
1537 | {
1538 | Cell *cells = NULL;
1539 | Raster buf;
1540 | unsigned int numPixels;
1541 |
1542 | numPixels = (unsigned int) image.width * (unsigned int) image.height;
1543 |
1544 | STACK_ALLOC(cells, Cell, 128 * 128, numPixels);
1545 | if (!cells) {
1546 | return -1;
1547 | }
1548 | memset(cells, 0, numPixels * sizeof *cells);
1549 | buf.cells = cells;
1550 | buf.width = image.width;
1551 | buf.height = image.height;
1552 |
1553 | transform_points(outl->numPoints, outl->points, transform);
1554 |
1555 | clip_points(outl->numPoints, outl->points, image.width, image.height);
1556 |
1557 | if (tesselate_curves(outl) < 0) {
1558 | STACK_FREE(cells);
1559 | return -1;
1560 | }
1561 |
1562 | draw_lines(outl, buf);
1563 |
1564 | post_process(buf, image.pixels);
1565 |
1566 | STACK_FREE(cells);
1567 | return 0;
1568 | }
1569 |
1570 |
--------------------------------------------------------------------------------
/schrift.h:
--------------------------------------------------------------------------------
1 | /* This file is part of libschrift.
2 | *
3 | * © 2019-2022 Thomas Oltmann and contributors
4 | *
5 | * Permission to use, copy, modify, and/or distribute this software for any
6 | * purpose with or without fee is hereby granted, provided that the above
7 | * copyright notice and this permission notice appear in all copies.
8 | *
9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
16 |
17 | #ifndef SCHRIFT_H
18 | #define SCHRIFT_H 1
19 |
20 | #include /* size_t */
21 | #include /* uint_fast32_t, uint_least32_t */
22 |
23 | #ifdef __cplusplus
24 | extern "C" {
25 | #endif
26 |
27 | #define SFT_DOWNWARD_Y 0x01
28 |
29 | typedef struct SFT SFT;
30 | typedef struct SFT_Font SFT_Font;
31 | typedef uint_least32_t SFT_UChar; /* Guaranteed to be compatible with char32_t. */
32 | typedef uint_fast32_t SFT_Glyph;
33 | typedef struct SFT_LMetrics SFT_LMetrics;
34 | typedef struct SFT_GMetrics SFT_GMetrics;
35 | typedef struct SFT_Kerning SFT_Kerning;
36 | typedef struct SFT_Image SFT_Image;
37 |
38 | struct SFT
39 | {
40 | SFT_Font *font;
41 | double xScale;
42 | double yScale;
43 | double xOffset;
44 | double yOffset;
45 | int flags;
46 | };
47 |
48 | struct SFT_LMetrics
49 | {
50 | double ascender;
51 | double descender;
52 | double lineGap;
53 | };
54 |
55 | struct SFT_GMetrics
56 | {
57 | double advanceWidth;
58 | double leftSideBearing;
59 | int yOffset;
60 | int minWidth;
61 | int minHeight;
62 | };
63 |
64 | struct SFT_Kerning
65 | {
66 | double xShift;
67 | double yShift;
68 | };
69 |
70 | struct SFT_Image
71 | {
72 | void *pixels;
73 | int width;
74 | int height;
75 | };
76 |
77 | const char *sft_version(void);
78 |
79 | SFT_Font *sft_loadmem (const void *mem, size_t size);
80 | SFT_Font *sft_loadfile(const char *filename);
81 | void sft_freefont(SFT_Font *font);
82 |
83 | int sft_lmetrics(const SFT *sft, SFT_LMetrics *metrics);
84 | int sft_lookup (const SFT *sft, SFT_UChar codepoint, SFT_Glyph *glyph);
85 | int sft_gmetrics(const SFT *sft, SFT_Glyph glyph, SFT_GMetrics *metrics);
86 | int sft_kerning (const SFT *sft, SFT_Glyph leftGlyph, SFT_Glyph rightGlyph,
87 | SFT_Kerning *kerning);
88 | int sft_render (const SFT *sft, SFT_Glyph glyph, SFT_Image image);
89 |
90 | #ifdef __cplusplus
91 | }
92 | #endif
93 |
94 | #endif
95 |
96 |
--------------------------------------------------------------------------------
/stress.c:
--------------------------------------------------------------------------------
1 | /* A stress testing program. Useful for profiling hot spots in libschrift. */
2 | /* See LICENSE file for copyright and license details. */
3 |
4 | #include
5 | #include
6 | #include
7 |
8 | #include
9 |
10 | #include "util/arg.h"
11 |
12 | char *argv0;
13 |
14 | static void
15 | die(const char *msg)
16 | {
17 | fprintf(stderr, "%s\n", msg);
18 | exit(1);
19 | }
20 |
21 | static void
22 | usage(void)
23 | {
24 | fprintf(stderr,
25 | "usage: %s [-f font file] [-s size in px]\n", argv0);
26 | }
27 |
28 | int
29 | main(int argc, char *argv[])
30 | {
31 | SFT sft;
32 | SFT_Font *font;
33 | const char *filename;
34 | double size;
35 | unsigned long cp;
36 | SFT_Glyph gid;
37 | SFT_GMetrics mtx;
38 | SFT_Image image;
39 | int i;
40 |
41 | filename = "resources/Ubuntu-Regular.ttf";
42 | size = 20.0;
43 |
44 | ARGBEGIN {
45 | case 'f':
46 | filename = EARGF(usage());
47 | break;
48 | case 's':
49 | size = atof(EARGF(usage()));
50 | break;
51 | default:
52 | usage();
53 | exit(1);
54 | } ARGEND
55 | if (argc) {
56 | usage();
57 | exit(1);
58 | }
59 |
60 | if (!(font = sft_loadfile(filename)))
61 | die("Can't load font file.");
62 | memset(&sft, 0, sizeof sft);
63 | sft.font = font;
64 | sft.xScale = size;
65 | sft.yScale = size;
66 | sft.flags = SFT_DOWNWARD_Y;
67 | for (i = 0; i < 5000; ++i) {
68 | for (cp = 32; cp < 128; ++cp) {
69 | if (sft_lookup(&sft, cp, &gid) < 0)
70 | continue;
71 | if (sft_gmetrics(&sft, gid, &mtx) < 0)
72 | continue;
73 | image.width = mtx.minWidth;
74 | image.height = mtx.minHeight;
75 | image.pixels = malloc((size_t) image.width * (size_t) image.height);
76 | sft_render(&sft, gid, image);
77 | free(image.pixels);
78 | }
79 | }
80 | sft_freefont(font);
81 | return 0;
82 | }
83 |
84 |
--------------------------------------------------------------------------------
/util/arg.h:
--------------------------------------------------------------------------------
1 | /*
2 | * ISC-License
3 | *
4 | * Copyright 2004-2017 Christoph Lohmann <20h@r-36.net>
5 | * Copyright 2017-2018 Laslo Hunhold
6 | *
7 | * Permission to use, copy, modify, and/or distribute this software for any
8 | * purpose with or without fee is hereby granted, provided that the above
9 | * copyright notice and this permission notice appear in all copies.
10 | *
11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 | */
19 | #ifndef ARG_H
20 | #define ARG_H
21 |
22 | extern char *argv0;
23 |
24 | /* int main(int argc, char *argv[]) */
25 | #define ARGBEGIN for (argv0 = *argv, *argv ? (argc--, argv++) : ((void *)0); \
26 | *argv && (*argv)[0] == '-' && (*argv)[1]; argc--, argv++) { \
27 | int i_, argused_; \
28 | if ((*argv)[1] == '-' && !(*argv)[2]) { \
29 | argc--, argv++; \
30 | break; \
31 | } \
32 | for (i_ = 1, argused_ = 0; (*argv)[i_]; i_++) { \
33 | switch((*argv)[i_])
34 | #define ARGEND if (argused_) { \
35 | if ((*argv)[i_ + 1]) { \
36 | break; \
37 | } else { \
38 | argc--, argv++; \
39 | break; \
40 | } \
41 | } \
42 | } \
43 | }
44 | #define ARGC() ((*argv)[i_])
45 | #define ARGF_(x) (((*argv)[i_ + 1]) ? (argused_ = 1, &((*argv)[i_ + 1])) : \
46 | (*(argv + 1)) ? (argused_ = 1, *(argv + 1)) : (x))
47 | #define EARGF(x) ARGF_(((x), exit(1), (char *)0))
48 | #define ARGF() ARGF_((char *)0)
49 |
50 | #endif
51 |
--------------------------------------------------------------------------------
/util/utf8_to_utf32.h:
--------------------------------------------------------------------------------
1 | /* needs stdint.h */
2 |
3 | #ifndef UTF8_TO_UTF32_H
4 | #define UTF8_TO_UTF32_H
5 |
6 | static int
7 | utf8_to_utf32(const uint8_t *utf8, uint32_t *utf32, int max)
8 | {
9 | unsigned int c;
10 | int i = 0;
11 | --max;
12 | while (*utf8) {
13 | if (i >= max)
14 | return 0;
15 | if (!(*utf8 & 0x80U)) {
16 | utf32[i++] = *utf8++;
17 | } else if ((*utf8 & 0xe0U) == 0xc0U) {
18 | c = (*utf8++ & 0x1fU) << 6;
19 | if ((*utf8 & 0xc0U) != 0x80U) return 0;
20 | utf32[i++] = c + (*utf8++ & 0x3fU);
21 | } else if ((*utf8 & 0xf0U) == 0xe0U) {
22 | c = (*utf8++ & 0x0fU) << 12;
23 | if ((*utf8 & 0xc0U) != 0x80U) return 0;
24 | c += (*utf8++ & 0x3fU) << 6;
25 | if ((*utf8 & 0xc0U) != 0x80U) return 0;
26 | utf32[i++] = c + (*utf8++ & 0x3fU);
27 | } else if ((*utf8 & 0xf8U) == 0xf0U) {
28 | c = (*utf8++ & 0x07U) << 18;
29 | if ((*utf8 & 0xc0U) != 0x80U) return 0;
30 | c += (*utf8++ & 0x3fU) << 12;
31 | if ((*utf8 & 0xc0U) != 0x80U) return 0;
32 | c += (*utf8++ & 0x3fU) << 6;
33 | if ((*utf8 & 0xc0U) != 0x80U) return 0;
34 | c += (*utf8++ & 0x3fU);
35 | if ((c & 0xFFFFF800U) == 0xD800U) return 0;
36 | utf32[i++] = c;
37 | } else return 0;
38 | }
39 | utf32[i] = 0;
40 | return i;
41 | }
42 |
43 | #endif
44 |
45 |
--------------------------------------------------------------------------------