├── .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 | ![demo screenshot](resources/demo-screenshot.png) 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 | Buy Me A Coffee 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 | --------------------------------------------------------------------------------