├── .github └── workflows │ └── build.yml ├── .gitignore ├── LICENSE ├── README.md ├── docs ├── nimdoc.out.css ├── notepadWindows.png ├── paintNetWindows.png ├── sketchMac.png ├── typography.html └── typography │ ├── font.html │ ├── layout.html │ ├── nimdoc.out.css │ ├── opentype.html │ ├── opentype │ ├── nimdoc.out.css │ ├── parser.html │ └── types.html │ ├── opentypedata.html │ ├── rasterizer.html │ ├── svg.html │ └── ttf.html ├── examples └── sdl.nim ├── src ├── typography.nim └── typography │ ├── font.nim │ ├── layout.nim │ ├── linebreak.nim │ ├── linebreak.txt │ ├── opentype │ ├── parser.nim │ └── types.nim │ ├── rasterizer.nim │ ├── svgfont.nim │ ├── systemfonts.nim │ └── textboxes.nim ├── tests ├── benchmark_otf.nim ├── common.nim ├── config.nims ├── fontglyphs.nim ├── fonts │ ├── Changa-Bold.svg │ ├── Changa-Bold.ttf │ ├── DejaVuSans.svg │ ├── DejaVuSans.ttf │ ├── IBMPlexSans-Regular.svg │ ├── IBMPlexSans-Regular.ttf │ ├── Jura-Regular.ttf │ ├── Lato-Regular.ttf │ ├── Moon Bold.otf │ ├── Moon-Bold.svg │ ├── SourceSansPro-Regular.ttf │ ├── SourceSansPro.otf │ ├── Ubuntu.svg │ ├── Ubuntu.ttf │ ├── Vera.ttf │ ├── hanazono │ │ ├── HanaMinA.ttf │ │ ├── HanaMinB.ttf │ │ ├── LICENSE.txt │ │ ├── README.txt │ │ └── THANKS.txt │ └── silver.ttf ├── fuzz_otf.nim ├── googlefonts │ ├── index.html │ └── masters │ │ ├── 0.png │ │ ├── 1.png │ │ ├── 10.png │ │ ├── 11.png │ │ ├── 12.png │ │ ├── 13.png │ │ ├── 14.png │ │ ├── 15.png │ │ ├── 16.png │ │ ├── 17.png │ │ ├── 18.png │ │ ├── 19.png │ │ ├── 2.png │ │ ├── 20.png │ │ ├── 21.png │ │ ├── 22.png │ │ ├── 23.png │ │ ├── 24.png │ │ ├── 25.png │ │ ├── 26.png │ │ ├── 27.png │ │ ├── 28.png │ │ ├── 29.png │ │ ├── 3.png │ │ ├── 30.png │ │ ├── 31.png │ │ ├── 32.png │ │ ├── 33.png │ │ ├── 34.png │ │ ├── 35.png │ │ ├── 36.png │ │ ├── 37.png │ │ ├── 38.png │ │ ├── 39.png │ │ ├── 4.png │ │ ├── 40.png │ │ ├── 5.png │ │ ├── 6.png │ │ ├── 7.png │ │ ├── 8.png │ │ └── 9.png ├── inspect_tableusage.nim ├── megatest.nim ├── rendered │ ├── alignment.png │ ├── basicSvg.png │ ├── basicTtf.png │ ├── basicTtf2.png │ ├── ch.png │ ├── font_metrics.png │ ├── font_metrics_blur.png │ ├── hFill.png │ ├── layout.png │ ├── layoutNoText.png │ ├── picking.png │ ├── qFill.png │ ├── qOutLine.png │ ├── ru.png │ ├── sample_blur.png │ ├── sample_svg.png │ ├── sample_ttf.png │ ├── scaledup.png │ ├── selection.png │ ├── sizes.png │ ├── subpixelglyphs.png │ ├── subpixelpos.png │ ├── tabs.png │ ├── test_char.png │ ├── test_charFill.png │ ├── test_otf.png │ ├── test_svg.png │ ├── test_ttf.png │ ├── wordwrap.png │ └── wordwrapch.png ├── samples │ ├── sample.ch.txt │ ├── sample.ch.wrap.txt │ ├── sample.fr.txt │ ├── sample.ko.txt │ ├── sample.ru.txt │ ├── sample.txt │ └── sample.wrap.txt ├── test.nim ├── test_char.nim ├── test_otf.nim ├── test_svg.nim ├── test_textboxes.nim ├── test_ttf.nim └── textbox │ ├── backspace_delete.png │ ├── copy.png │ ├── cut.png │ ├── jump_down_0.png │ ├── jump_down_1.png │ ├── jump_down_2.png │ ├── jump_down_3.png │ ├── jump_up_0.png │ ├── jump_up_1.png │ ├── jump_up_2.png │ ├── jump_up_3.png │ ├── left_right.gif │ ├── left_right.png │ ├── left_right_shift_0.png │ ├── left_right_shift_1.png │ ├── left_right_shift_10.png │ ├── left_right_shift_11.png │ ├── left_right_shift_12.png │ ├── left_right_shift_13.png │ ├── left_right_shift_14.png │ ├── left_right_shift_15.png │ ├── left_right_shift_16.png │ ├── left_right_shift_17.png │ ├── left_right_shift_18.png │ ├── left_right_shift_19.png │ ├── left_right_shift_2.png │ ├── left_right_shift_20.png │ ├── left_right_shift_21.png │ ├── left_right_shift_22.png │ ├── left_right_shift_23.png │ ├── left_right_shift_3.png │ ├── left_right_shift_4.png │ ├── left_right_shift_5.png │ ├── left_right_shift_6.png │ ├── left_right_shift_7.png │ ├── left_right_shift_8.png │ ├── left_right_shift_9.png │ ├── paste_0.png │ ├── paste_1.png │ ├── picking_0.png │ ├── picking_1.png │ ├── picking_10.png │ ├── picking_2.png │ ├── picking_3.png │ ├── picking_4.png │ ├── picking_5.png │ ├── picking_6.png │ ├── picking_7.png │ ├── picking_8.png │ ├── picking_9.png │ ├── plain.png │ ├── scroll_0.png │ ├── scroll_1.png │ ├── scroll_10.png │ ├── scroll_11.png │ ├── scroll_12.png │ ├── scroll_13.png │ ├── scroll_14.png │ ├── scroll_15.png │ ├── scroll_16.png │ ├── scroll_17.png │ ├── scroll_18.png │ ├── scroll_19.png │ ├── scroll_2.png │ ├── scroll_20.png │ ├── scroll_21.png │ ├── scroll_22.png │ ├── scroll_3.png │ ├── scroll_4.png │ ├── scroll_5.png │ ├── scroll_6.png │ ├── scroll_7.png │ ├── scroll_8.png │ ├── scroll_9.png │ ├── select.gif │ ├── select_all.png │ ├── select_peragraph.png │ ├── select_word.png │ ├── typing.png │ ├── up_down_0.png │ ├── up_down_1.png │ ├── up_down_2.png │ ├── up_down_3.png │ ├── up_down_4.png │ ├── up_down_5.png │ ├── up_down_6.png │ ├── up_down_7.png │ ├── up_down_8.png │ ├── up_down_9.png │ └── word_backspace_delete.png └── typography.nimble /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Run tests 2 | on: [push, pull_request] 3 | jobs: 4 | build: 5 | runs-on: ubuntu-latest 6 | 7 | steps: 8 | - uses: actions/checkout@v1 9 | 10 | - name: Cache choosenim 11 | id: cache-choosenim 12 | uses: actions/cache@v1 13 | with: 14 | path: ~/.choosenim 15 | key: ${{ runner.os }}-choosenim-stable 16 | 17 | - name: Cache nimble 18 | id: cache-nimble 19 | uses: actions/cache@v1 20 | with: 21 | path: ~/.nimble 22 | key: ${{ runner.os }}-nimble-stable 23 | 24 | - uses: jiro4989/setup-nim-action@v1 25 | 26 | - run: nimble test -y 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # ignore files with no extention: 3 | * 4 | !*/ 5 | !*.* 6 | 7 | # normal ignores: 8 | .* 9 | *.exe 10 | *.dll 11 | nimcache 12 | *.idx 13 | 14 | # Do to license issues ignore some fonts: 15 | tests/fonts/helvetica.ttf 16 | 17 | tests/googlefonts/diffs 18 | tests/googlefonts/out 19 | tests/systemfonts 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT 2 | 3 | Copyright 2018 Andre von Houck 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Typography - Fonts, Typesetting and Rasterization. 2 | 3 | ⚠️ Typography has been integrated into [Pixie](https://github.com/treeform/pixie), a full-featured 2D graphics library for Nim. This repo is no longer being actively worked on. ⚠️ 4 | 5 | Pixie supports more TrueType / OpenType font features as well as rich text layout and rasterizizing through spans. 6 | 7 | Consider migrating projects from Typography to [Pixie](https://github.com/treeform/pixie) for additional functionality and future improvements. 🚀🚀🚀🚀🚀 8 | 9 | ---- 10 | 11 | `nimble install typography` 12 | 13 | [![Run tests](https://github.com/treeform/typography/actions/workflows/build.yml/badge.svg)](https://github.com/treeform/typography/actions/workflows/build.yml) 14 | 15 | [API reference](https://nimdocs.com/treeform/typography) 16 | 17 | ## About 18 | 19 | Typography is pure nim implementation for font rasterization (letter drawing) and text typesetting (text layout). It does *not* rely on any external library such as FreeType, stb_truetype, pango or HarfBuzz. 20 | 21 | See api reference: https://treeform.github.io/typography/typography.html 22 | 23 | # Font file formats: 24 | * SVG fonts - Most features are supported. 25 | * TTF fonts - Fair support. Most modern features are supported but font format came out in 1994 and has a bunch of formats for different OSes that are not supported. 26 | * OTF fonts - Basic TTF outline support only. No support for CFF or SVG outlines. 27 | 28 | # Requred Packages 29 | 30 | * [vmath](https://github.com/treeform/vmath) - vector stuff, vec2 and matrices. 31 | * [pixie](https://github.com/treeform/pixie) - image stuff, saving and loading PNGs. 32 | * [chroma](https://github.com/treeform/chroma) - color stuff, mostly to save and add rgba colors. 33 | * [print](https://github.com/treeform/print) - better logging. 34 | * [bumpy](https://github.com/treeform/bumpy) - geometry stuff. 35 | * [flatty](https://github.com/treeform/flatty) - dealing with binary encoding. 36 | 37 | # Basic usage 38 | 39 | ```nim 40 | var font = readFontSvg("fonts/Ubuntu.svg") 41 | font.drawText(image, vec2(10, 50), "The quick brown fox jumps over the lazy dog.") 42 | ``` 43 | 44 | ![example output](tests/rendered/basicSvg.png?raw=true) 45 | 46 | ```nim 47 | var font = readFontTtf("fonts/Ubuntu.ttf") 48 | font.drawText(image, vec2(10, 50), "The quick brown fox jumps over the lazy dog.") 49 | ``` 50 | 51 | ![example output](tests/rendered/basicTtf.png?raw=true) 52 | 53 | ```nim 54 | font.size = 8 55 | font.drawText(image, vec2(10, 10), "The quick brown fox jumps over the lazy dog.") 56 | font.size = 10 57 | font.drawText(image, vec2(10, 25), "The quick brown fox jumps over the lazy dog.") 58 | font.size = 14 59 | font.drawText(image, vec2(10, 45), "The quick brown fox jumps over the lazy dog.") 60 | font.size = 22 61 | font.drawText(image, vec2(10, 75), "The quick brown fox jumps over the lazy dog.") 62 | ``` 63 | 64 | ![example output](tests/rendered/sizes.png?raw=true) 65 | 66 | ```nim 67 | font.drawText(image, vec2(10, 10), readFile("examples/sample.ru.txt")) 68 | ``` 69 | ![example output](tests/rendered/ru.png?raw=true) 70 | 71 | # Dealing with Glyphs 72 | 73 | Each font has an table of glyphs. 74 | ```nim 75 | font.glyphs["Q"] 76 | ``` 77 | And for each glyphs, you can see what the SVG path of a glyph looks like: 78 | ```nim 79 | echo font.glyphs["Q"].path 80 | ``` 81 | ``` 82 | M754,236 Q555,236 414,377 Q273,518 273,717 Q273,916 414,1057 Q555,1198 754,1198 Q953,1198 1094,1057 Q1235,916 1235,717 Q1235,593 1175,485 L1096,565 Q1062,599 1013,599 Q964,599 929,565 Q895,530 895,481 Q895,432 929,398 L1014,313 Q895,236 754,236 Z M1347,314 Q1471,496 1471,717 Q1471,1014 1261,1224 Q1051,1434 754,1434 Q458,1434 247,1224 Q37,1014 37,717 Q37,421 247,210 Q458,0 754,0 Q993,0 1184,143 L1292,35 Q1327,0 1376,0 Q1425,0 1459,35 Q1494,69 1494,118 Q1494,167 1459,201 Z 83 | ``` 84 | You can also draw this path to see all of the paths and all of the curve contorl points: 85 | ```nim 86 | font.getGlyphOutlineImage("Q") 87 | ``` 88 | ![example output](tests/rendered/qOutLine.png?raw=true) 89 | 90 | Most of the time you would like to just get the image instead: 91 | 92 | ```nim 93 | font.getGlyphImage("Q") 94 | ``` 95 | ![example output](tests/rendered/qFill.png?raw=true) 96 | 97 | You can then use this image in openGL, canvas, or even HTML. 98 | 99 | # Subpixel glyphs with subpixel layout: 100 | 101 | Each glyphs can be rendered with a subpixel offset, so that it fits into the layout: 102 | 103 | ![example output](tests/rendered/subpixelpos.png?raw=true) 104 | 105 | Note how many of the "o"s and "m"s are different from each other. This happens because spaces between letters are not an integer number of pixels so glyphs must be rendred shifted by fraction of a pixel. 106 | 107 | Here is how glyph changes with different subpixel offsets: 108 | 109 | ![example output](tests/rendered/subpixelglyphs.png?raw=true) 110 | 111 | ```nim 112 | var glyphOffset # this is an offset of the image from the 0,0 position 113 | var image = font.getGlyphImage(glyph, glyphOffset, subPixelShift=X) 114 | ``` 115 | 116 | # Typesetting 117 | 118 | Before glyphs can be rendered they need to be typeset: 119 | 120 | ```nim 121 | var layout = font.typeset(""" 122 | Two roads diverged in a yellow wood, 123 | And sorry I could not travel both 124 | And be one traveler, long I stood 125 | And looked down one as far as I could 126 | To where it bent in the undergrowth;""") 127 | ``` 128 | 129 | This produces a layout. 130 | 131 | ![example output](tests/rendered/layoutNoText.png?raw=true) 132 | 133 | ## Drawing the layout 134 | You can then use the simple drawing included to draw to an image, or use some other graphical librarry like openGL, canvas, or even HTML: 135 | 136 | ```nim 137 | image.drawText(layout) 138 | ``` 139 | ![example output](tests/rendered/layout.png?raw=true) 140 | 141 | ## Wrapping and Clipping 142 | 143 | You can also give the typeset region width and height so that text wraps and clips: 144 | 145 | ```nim 146 | font.typeset( 147 | readFile("sample.wrap.txt"), 148 | pos=vec2(100, 20), 149 | size=vec2(300, 160) 150 | ) 151 | ``` 152 | 153 | ![example output](tests/rendered/wordwrap.png?raw=true) 154 | 155 | ## Alignment 156 | 157 | There are 3 horizontal and 3 vertical alignment modes: 158 | 159 | ```nim 160 | font.typeset("Center, Bottom", 161 | pos=vec2(20, 20), 162 | size=vec2(460, 160), 163 | hAlign=Center, 164 | vAlign=Bottom 165 | ) 166 | ``` 167 | 168 | ![example output](tests/rendered/alignment.png?raw=true) 169 | 170 | ## Selection 171 | 172 | When selecting text is useful to know where to highlighting rectangles. 173 | 174 | ```nim 175 | layout.getSelection(23, 120) # selects char 23 to char 120 (not glyphs) 176 | ``` 177 | 178 | ![example output](tests/rendered/selection.png?raw=true) 179 | 180 | ## Picking 181 | 182 | When clicking on text is useful to know where to highlighting what glyph and what is the string index. 183 | 184 | ```nim 185 | layout.pickGlyphAt(vec2(120, 48)) # selects glyph at cordiantes 186 | ``` 187 | 188 | ![example output](tests/rendered/picking.png?raw=true) 189 | 190 | 191 | # Comparison to different OSs. 192 | 193 | At the large font sizes (more then 24 pixels) the fonts on most operating system looks nearly identical. But when you scale the font below 24px different OSs take different approaches. 194 | * OSX - tries to render fonts most true to how font designer intended even if they look a blurry. 195 | * Windows - tries to render fonts to a pixel grid making them look sharper. 196 | * Linux - configurable and somewhere between the two. 197 | * iOS, Android - it really does not matter how the font is rendered because its almost always above 24px because of high resolution screens phones have. 198 | 199 | ```nim 200 | var font = readFontSvg("fonts/DejaVuSans.svg") 201 | font.size = 11 # 11px or 8pt 202 | font.drawText(image, vec2(10, 15), "The quick brown fox jumps over the lazy dog.") 203 | ``` 204 | 205 | Typography renderer - **this library** (4x): 206 | 207 | ![example output](tests/rendered/scaledup.png?raw=true) 208 | 209 | [Apple Core Text](https://developer.apple.com/documentation/coretext) renderer (4x): 210 | 211 | ![example output](docs/sketchMac.png?raw=true) 212 | 213 | [Paint.net](Paint.net) renderer (4x): 214 | 215 | ![example output](docs/paintNetWindows.png?raw=true) 216 | 217 | [Bohemian Sketch](https://www.sketch.com/) renderer (4x): 218 | 219 | ![example output](docs/sketchMac.png?raw=true) 220 | 221 | [Window ClearType](https://docs.microsoft.com/en-us/typography/cleartype/) renderer (4x): 222 | 223 | ![example output](docs/notepadWindows.png?raw=true) 224 | 225 | How the font should look on screen is very subjective, some people love the crisp windows fonts, others swear by the apples adherence to design. But my opinion is it's all related a lot with familiarity. What you are used to is what you would like best, and when a person switches to a different screen with a different font rendering style brain immediately rejects it. 226 | 227 | # Subpixel Antialising is on its way out 228 | 229 | About a decade ago use of Subpixel Antialising improved readability of fonts. It would leak a bit of color to the left and right of text because color pixels were not square. The monitors followed predictable pixel patterns first in CRTs then in LCDs. 230 | 231 | ![example output](https://upload.wikimedia.org/wikipedia/commons/5/57/Subpixel-rendering-RGB.png?raw=true) 232 | 233 | Then everything changed. Today our pixels small and they don't follow a typical CRT or LCD orientation. 234 | 235 | ![example output](https://upload.wikimedia.org/wikipedia/commons/thumb/4/4d/Pixel_geometry_01_Pengo.jpg/220px-Pixel_geometry_01_Pengo.jpg) 236 | 237 | The fact that there is no standard pixel layout grid anymore. And the fact that high resolution displays are everywhere makes subpixeling obsolete. Apple, Adobe, Bohemian and others in the typography space are abandoning subpixeling. 238 | 239 | This library does not support Subpixel Antialising. 240 | 241 | [Neat tricks](http://www.typophile.com/node/60577) with [Subpixel rendering](http://www.typophile.com/node/61920) 242 | 243 | [Apple removes Subpixel Antialising](https://www.reddit.com/r/apple/comments/8wpk18/macos_mojave_nukes_subpixel_antialiasing_making/) 244 | 245 | # Text Boxes 246 | 247 | Full backend implementation of a text area. You need to connect your own rendering, keyboard and mouse input. 248 | 249 | Often when displaying text you also need to edit text. This is where the textbox part of this library comes in. This implemented the backend of a text box/text area/input element/text field. Text boxes are surprisingly hard to implement right because the users are very familiar with how they work, so any missing features or inconsistencies are painfully obvious. 250 | 251 | Here is a small list of some of the features: 252 | 253 | * Typing, arrow keys, backspace and delete. 254 | * Mouse clicking, dragging to select. 255 | * Almost everything can be used to select with shift. 256 | * Double click to select word, triple click to select paragraph, quad click to select all. 257 | * Page up and page down. 258 | * Move left/right by word. 259 | * Move to start or end of lines. 260 | * Copy, Cut and Paste. 261 | * Scrolling and scroll to cursor when typing or selecting. 262 | * Resizing of the text box while it’s being added. 263 | * Remember your horizontal position when going up or down with short lines. 264 | 265 | ![example output](tests/textbox/left_right.gif?raw=true) 266 | 267 | ![example output](tests/textbox/select.gif?raw=true) 268 | 269 | 270 | # How to convert any font to SVG font using FontForge: 271 | 272 | SVG fonts are really nice. The are simple to parse and understand and debug. They are very uncommon though. But they are good as a debug input, output, or intermediate step. 273 | 274 | ```bash 275 | $ fontforge -c 'Open($1); Generate($2)' foo.ttf foo.svg 276 | ``` 277 | -------------------------------------------------------------------------------- /docs/notepadWindows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/docs/notepadWindows.png -------------------------------------------------------------------------------- /docs/paintNetWindows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/docs/paintNetWindows.png -------------------------------------------------------------------------------- /docs/sketchMac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/docs/sketchMac.png -------------------------------------------------------------------------------- /docs/typography.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | typography 21 | 22 | 23 | 24 | 25 | 69 | 70 | 71 | 72 |
73 |
74 |

typography

75 |
76 |
77 |
78 | 82 |     Dark Mode 83 |
84 | 88 |
89 | Search: 91 |
92 |
93 | Group by: 94 | 98 |
99 |
    100 |
  • 101 | Imports 102 |
      103 | 104 |
    105 |
  • 106 |
  • 107 | Exports 108 |
      109 | 110 |
    111 |
  • 112 | 113 |
114 | 115 |
116 | 132 |
133 | 134 |
135 | 140 |
141 |
142 |
143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /docs/typography/svg.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | typography/svg 21 | 22 | 23 | 24 | 25 | 69 | 70 | 71 | 72 |
73 |
74 |

typography/svg

75 |
76 |
77 |
78 | 82 |     Dark Mode 83 |
84 | 88 |
89 | Search: 91 |
92 |
93 | Group by: 94 | 98 |
99 | 118 | 119 |
120 |
121 |
122 | 123 |

124 |
125 |

Imports

126 |
127 | font 128 |
129 |
130 |

Procs

131 |
132 | 133 |
proc readFontSvg(f: Stream): Font {...}{.raises: [Defect, IOError, OSError, ValueError,
134 |                                         Exception, XmlError],
135 |                                 tags: [ReadIOEffect, RootEffect, WriteIOEffect].}
136 |
137 | 138 | Read an SVG font from a stream. 139 | 140 |
141 | 142 |
proc readFontSvg(filename: string): Font {...}{.raises: [OSError, Defect, IOError,
143 |     ValueError, Exception, XmlError], tags: [ReadDirEffect, ReadIOEffect, RootEffect,
144 |     WriteIOEffect].}
145 |
146 | 147 | Read an SVG font from a file. 148 | 149 |
150 | 151 |
152 | 153 |
154 |
155 | 156 |
157 | 162 |
163 |
164 |
165 | 166 | 167 | 168 | -------------------------------------------------------------------------------- /examples/sdl.nim: -------------------------------------------------------------------------------- 1 | ## Bare-bones SDL2 example 2 | 3 | import chroma, pixie, hashes, sdl2, tables, times, typography, unicode, vmath 4 | 5 | discard sdl2.init(INIT_EVERYTHING) 6 | 7 | var 8 | window: WindowPtr 9 | render: RendererPtr 10 | 11 | window = createWindow("SDL Skeleton", 100, 100, 640, 480, SDL_WINDOW_SHOWN) 12 | render = createRenderer( 13 | window, 14 | -1, 15 | Renderer_Accelerated or Renderer_PresentVsync or Renderer_TargetTexture 16 | ) 17 | 18 | # load font 19 | var font = readFontTtf("fonts/Moon Bold.otf") 20 | font.size = 16 21 | 22 | var 23 | evt = sdl2.defaultEvent 24 | runGame = true 25 | 26 | type GlyphKey = object 27 | rune: Rune 28 | size: float 29 | subPixelShift: int 30 | 31 | proc hash(x: GlyphKey): Hash = 32 | hashData(unsafeAddr x, sizeOf x) 33 | 34 | type GlyphEntry = object 35 | image: Image 36 | texture: TexturePtr 37 | glyphOffset: Vec2 38 | 39 | var glyphCache = newTable[GlyphKey, GlyphEntry]() 40 | 41 | proc texture(image: Image): TexturePtr = 42 | # convert a pixie image to a SDL texture 43 | const 44 | rmask = uint32 0x000000ff 45 | gmask = uint32 0x0000ff00 46 | bmask = uint32 0x00ff0000 47 | amask = uint32 0xff000000 48 | var surface = createRGBSurface( 49 | 0, 50 | cint image.width, 51 | cint image.height, 52 | 32, 53 | rmask, 54 | gmask, 55 | bmask, 56 | amask 57 | ) 58 | surface.pixels = addr image.data[0] 59 | var texture = render.createTextureFromSurface(surface) 60 | return texture 61 | 62 | while runGame: 63 | while pollEvent(evt): 64 | if evt.kind == QuitEvent: 65 | runGame = false 66 | break 67 | 68 | render.setDrawColor 0, 0, 0, 255 69 | render.clear 70 | 71 | let start = epochTime() 72 | 73 | when false: 74 | # draw a single letter 75 | font.size = 300 76 | font.lineHeight = 300 77 | var image = font.getGlyphImage("Q") 78 | var destRect = sdl2.rect( 79 | cint 0, 80 | cint 0, 81 | cint image.width, 82 | cint image.height 83 | ) 84 | sdl2.copy(render, image.texture, nil, addr destRect) 85 | 86 | when false: 87 | # compute layout and draw full layout at once 88 | var layout = font.typeset(""" 89 | Two roads diverged in a yellow wood, 90 | And sorry I could not travel both 91 | And be one traveler, long I stood 92 | And looked down one as far as I could 93 | To where it bent in the undergrowth;""") 94 | 95 | # draw text at a layout 96 | var image = newImage(500, 100) 97 | image.drawText(layout) 98 | 99 | var destRect = sdl2.rect( 100 | cint 0, 101 | cint 0, 102 | cint image.width, 103 | cint image.height 104 | ) 105 | sdl2.copy(render, image.texture, nil, addr destRect) 106 | 107 | when false: 108 | # draw single letter as a SDL texture 109 | 110 | var layout = font.typeset(""" 111 | Two roads diverged in a yellow wood, 112 | And sorry I could not travel both 113 | And be one traveler, long I stood 114 | And looked down one as far as I could 115 | To where it bent in the undergrowth;""") 116 | 117 | for pos in layout: 118 | var font = pos.font 119 | if pos.character in font.glyphs: 120 | var glyph = font.glyphs[pos.character] 121 | var glyphOffset: Vec2 122 | let image = font.getGlyphImage( 123 | glyph, 124 | glyphOffset, 125 | subPixelShift = pos.subPixelShift 126 | ) 127 | var destRect = sdl2.rect( 128 | cint pos.rect.x + glyphOffset.x, 129 | cint pos.rect.y + glyphOffset.y, 130 | cint image.width, 131 | cint image.height 132 | ) 133 | sdl2.copy(render, image.texture, nil, addr destRect) 134 | 135 | when true: 136 | # draw single letter at a time caching the letters as SDL texture 137 | 138 | var layout = font.typeset(""" 139 | Two roads diverged in a yellow wood, 140 | And sorry I could not travel both 141 | And be one traveler, long I stood 142 | And looked down one as far as I could 143 | To where it bent in the undergrowth;""") 144 | 145 | for pos in layout: 146 | var font = pos.font 147 | if pos.character in font.glyphs: 148 | let key = GlyphKey( 149 | rune: pos.rune, 150 | size: font.size, 151 | subPixelShift: int(pos.subPixelShift * 10) 152 | ) 153 | 154 | if key notin glyphCache: 155 | var glyph = font.glyphs[pos.character] 156 | var glyphOffset: Vec2 157 | let image = font.getGlyphImage( 158 | glyph, 159 | glyphOffset, 160 | subPixelShift = quantize(pos.subPixelShift, 0.1) 161 | ) 162 | glyphCache[key] = GlyphEntry( 163 | image: image, 164 | texture: image.texture, 165 | glyphOffset: glyphOffset 166 | ) 167 | echo "new", key 168 | 169 | let glyphEntry = glyphCache[key] 170 | var destRect = sdl2.rect( 171 | cint pos.rect.x + glyphEntry.glyphOffset.x, 172 | cint pos.rect.y + glyphEntry.glyphOffset.y, 173 | cint glyphEntry.image.width, 174 | cint glyphEntry.image.height 175 | ) 176 | 177 | discard glyphEntry.texture.setTextureColorMod(255, 0, 0) 178 | sdl2.copy(render, glyphEntry.texture, nil, addr destRect) 179 | 180 | #echo epochTime() - start 181 | 182 | render.present 183 | 184 | destroy render 185 | destroy window 186 | -------------------------------------------------------------------------------- /src/typography.nim: -------------------------------------------------------------------------------- 1 | import typography/font, typography/layout, typography/opentype/parser, 2 | typography/rasterizer 3 | 4 | export font, layout, parser, rasterizer 5 | -------------------------------------------------------------------------------- /src/typography/font.nim: -------------------------------------------------------------------------------- 1 | import bumpy, tables, vmath, opentype/types, pixie/paths, sequtils, std/fenv 2 | 3 | type 4 | Glyph* = ref object 5 | ## Contains information about Glyphs or "letters" 6 | ## SVG Path, command buffer, and shapes of lines. 7 | code*: string 8 | advance*: float32 9 | path*: Path 10 | shapes*: seq[seq[Vec2]] 11 | segments*: seq[seq[Segment]] 12 | bboxMin*: Vec2 13 | bboxMax*: Vec2 14 | ready*: bool 15 | isEmpty*: bool 16 | numberOfContours*: int 17 | isComposite*: bool 18 | index*: int 19 | pathStr*: string # SVG 20 | 21 | Typeface* = ref object 22 | ## Main font object contains font information and Glyphs 23 | filename*: string 24 | name*: string 25 | bboxMin*: Vec2 26 | bboxMax*: Vec2 27 | advance*: float32 28 | ascent*: float32 29 | descent*: float32 30 | xHeight*: float32 31 | capHeight*: float32 32 | unitsPerEm*: float32 33 | lineGap*: float32 34 | glyphs*: Table[string, Glyph] 35 | kerning*: Table[(string, string), float32] 36 | glyphArr*: seq[Glyph] 37 | otf*: OTFFont 38 | 39 | Font* = ref object 40 | ## Contains size, weight and typeface. 41 | typeface*: Typeface 42 | size*: float32 43 | lineHeight*: float32 44 | weight*: float32 45 | 46 | TypographyError* = object of ValueError 47 | 48 | proc `sizePt`*(font: Font): float32 = 49 | ## Gets font size in Pt or Point units. 50 | font.size * 0.75 51 | 52 | proc `sizePt=`*(font: Font, sizePoints: float32) = 53 | ## Sets font size in Pt or Point units. 54 | font.size = sizePoints / 0.75 55 | 56 | proc `sizeEm`*(font: Font): float32 = 57 | ## Gets font size in em units. 58 | font.size / 12 59 | 60 | proc `sizeEm=`*(font: Font, sizeEm: float32) = 61 | ## Gets font size in em units. 62 | font.size = sizeEm * 12 63 | 64 | proc `sizePr`*(font: Font): float32 = 65 | ## Gets font size in % or Percent units. 66 | font.size / 1200 67 | 68 | proc `sizePr=`*(font: Font, sizePercent: float32) = 69 | ## Gets font size in % or Percent units. 70 | font.size = sizePercent * 1200 71 | 72 | proc scale*(font: Font): float32 = 73 | ## Gets the internal scaling of font units to pixles. 74 | font.size / font.typeface.unitsPerEm 75 | 76 | proc letterHeight*(font: Font): float32 = 77 | ## Gets the current letter height based on ascent and descent and the current 78 | ## size and lineheight. 79 | (font.typeface.ascent - font.typeface.descent) * font.scale 80 | 81 | proc baseline*(font: Font): float32 = 82 | ## Gets the baseline of the font based on current size and lineheight. 83 | font.lineHeight / 2 - font.size / 2 + (font.size - font.letterHeight) / 2 + 84 | font.typeface.ascent * font.scale 85 | 86 | proc capline*(font: Font): float32 = 87 | ## Gets the current capline of the font based on current size and lineheight. 88 | font.baseline - font.typeface.capHeight * font.scale 89 | 90 | proc glyphPathToCommands*(glyph: Glyph) = 91 | ## Converts a glyph into lines-shape 92 | glyph.path = parsePath(glyph.pathStr) 93 | 94 | type 95 | PathShim* = ref object 96 | ## Used to hold paths and create paths. 97 | commands: seq[float32] 98 | start, at: Vec2 # Maintained by moveTo, lineTo, etc. Used by arcTo. 99 | 100 | PathCommandKind = enum 101 | ## Type of path commands 102 | Close, 103 | Move, Line, HLine, VLine, Cubic, SCubic, Quad, TQuad, Arc, 104 | RMove, RLine, RHLine, RVLine, RCubic, RSCubic, RQuad, RTQuad, RArc 105 | 106 | proc parameterCount(kind: PathCommandKind): int = 107 | ## Returns number of parameters a path command has. 108 | case kind: 109 | of Close: 0 110 | of Move, Line, RMove, RLine, TQuad, RTQuad: 2 111 | of HLine, VLine, RHLine, RVLine: 1 112 | of Cubic, RCubic: 6 113 | of SCubic, RSCubic, Quad, RQuad: 4 114 | of Arc, RArc: 7 115 | 116 | proc commandsToShapes( 117 | path: Path, closeSubpaths: bool, pixelScale: float32 118 | ): seq[Polygon] = 119 | ## Converts SVG-like commands to sequences of vectors. 120 | var 121 | start, at: Vec2 122 | shape: Polygon 123 | 124 | # Some commands use data from the previous command 125 | var 126 | prevCommandKind = Move 127 | prevCtrl, prevCtrl2: Vec2 128 | 129 | let errorMarginSq = pow(0.2 / pixelScale, 2) 130 | 131 | proc addSegment(shape: var Polygon, at, to: Vec2) = 132 | # Don't add any 0 length lines 133 | if at - to != vec2(0, 0): 134 | # Don't double up points 135 | if shape.len == 0 or shape[^1] != at: 136 | shape.add(at) 137 | shape.add(to) 138 | 139 | proc addCubic(shape: var Polygon, at, ctrl1, ctrl2, to: Vec2) = 140 | ## Adds cubic segments to shape. 141 | proc compute(at, ctrl1, ctrl2, to: Vec2, t: float32): Vec2 {.inline.} = 142 | let 143 | t2 = t*t 144 | t3 = t2*t 145 | at * (-t3 + 3*t2 - 3*t + 1) + 146 | ctrl1 * (3*t3 - 6*t2 + 3*t) + 147 | ctrl2 * (-3*t3 + 3*t2) + 148 | to * (t3) 149 | 150 | proc computeDeriv(at, ctrl1, ctrl2, to: Vec2, t: float32): Vec2 {.inline.} = 151 | let t2 = t*t 152 | at * (-3*t2 + 6*t - 3) + 153 | ctrl1 * (9*t2 - 12*t + 3) + 154 | ctrl2 * (-9*t2 + 6*t) + 155 | to * (3 * t2) 156 | 157 | var 158 | t: float32 # Where we are at on the curve from [0, 1] 159 | step = 1.float32 # How far we want to try to move along the curve 160 | prev = at 161 | next = compute(at, ctrl1, ctrl2, to, t + step) 162 | halfway = compute(at, ctrl1, ctrl2, to, t + step / 2) 163 | while true: 164 | if step <= epsilon(float32): 165 | raise newException(TypographyError, "Unable to discretize cubic") 166 | let 167 | midpoint = (prev + next) / 2 168 | lineTangent = midpoint - prev 169 | curveTangent = computeDeriv(at, ctrl1, ctrl2, to, t + step / 2) 170 | curveTangentScaled = curveTangent.normalize() * lineTangent.length() 171 | error = (midpoint - halfway).lengthSq 172 | errorTangent = (lineTangent - curveTangentScaled).lengthSq 173 | if error + errorTangent > errorMarginSq: 174 | next = halfway 175 | halfway = compute(at, ctrl1, ctrl2, to, t + step / 4) 176 | step /= 2 177 | else: 178 | shape.addSegment(prev, next) 179 | t += step 180 | if t == 1: 181 | break 182 | prev = next 183 | step = min(step * 2, 1 - t) # Optimistically attempt larger steps 184 | next = compute(at, ctrl1, ctrl2, to, t + step) 185 | halfway = compute(at, ctrl1, ctrl2, to, t + step / 2) 186 | 187 | proc addQuadratic(shape: var Polygon, at, ctrl, to: Vec2) = 188 | ## Adds quadratic segments to shape. 189 | proc compute(at, ctrl, to: Vec2, t: float32): Vec2 {.inline.} = 190 | let t2 = t*t 191 | at * (t2 - 2*t + 1) + 192 | ctrl * (-2*t2 + 2*t) + 193 | to * t2 194 | 195 | var 196 | t: float32 # Where we are at on the curve from [0, 1] 197 | step = 1.float32 # How far we want to try to move along the curve 198 | prev = at 199 | next = compute(at, ctrl, to, t + step) 200 | halfway = compute(at, ctrl, to, t + step / 2) 201 | halfStepping = false 202 | while true: 203 | if step <= epsilon(float32): 204 | raise newException(TypographyError, "Unable to discretize quadratic") 205 | let 206 | midpoint = (prev + next) / 2 207 | error = (midpoint - halfway).lengthSq 208 | if error > errorMarginSq: 209 | next = halfway 210 | halfway = compute(at, ctrl, to, t + step / 4) 211 | halfStepping = true 212 | step /= 2 213 | else: 214 | shape.addSegment(prev, next) 215 | t += step 216 | if t == 1: 217 | break 218 | prev = next 219 | if halfStepping: 220 | step = min(step, 1 - t) 221 | else: 222 | step = min(step * 2, 1 - t) # Optimistically attempt larger steps 223 | next = compute(at, ctrl, to, t + step) 224 | halfway = compute(at, ctrl, to, t + step / 2) 225 | 226 | proc addArc( 227 | shape: var Polygon, 228 | at, radii: Vec2, 229 | rotation: float32, 230 | large, sweep: bool, 231 | to: Vec2 232 | ) = 233 | ## Adds arc segments to shape. 234 | type ArcParams = object 235 | radii: Vec2 236 | rotMat: Mat3 237 | center: Vec2 238 | theta, delta: float32 239 | 240 | proc endpointToCenterArcParams( 241 | at, radii: Vec2, rotation: float32, large, sweep: bool, to: Vec2 242 | ): ArcParams = 243 | var 244 | radii = vec2(abs(radii.x), abs(radii.y)) 245 | radiiSq = vec2(radii.x * radii.x, radii.y * radii.y) 246 | 247 | let 248 | radians: float32 = rotation / 180 * PI 249 | d = vec2((at.x - to.x) / 2.0, (at.y - to.y) / 2.0) 250 | p = vec2( 251 | cos(radians) * d.x + sin(radians) * d.y, 252 | -sin(radians) * d.x + cos(radians) * d.y 253 | ) 254 | pSq = vec2(p.x * p.x, p.y * p.y) 255 | 256 | let cr = pSq.x / radiiSq.x + pSq.y / radiiSq.y 257 | if cr > 1: 258 | radii *= sqrt(cr) 259 | radiiSq = vec2(radii.x * radii.x, radii.y * radii.y) 260 | 261 | let 262 | dq = radiiSq.x * pSq.y + radiiSq.y * pSq.x 263 | pq = (radiiSq.x * radiiSq.y - dq) / dq 264 | 265 | var q = sqrt(max(0, pq)) 266 | if large == sweep: 267 | q = -q 268 | 269 | proc svgAngle(u, v: Vec2): float32 = 270 | let 271 | dot = dot(u, v) 272 | len = length(u) * length(v) 273 | result = arccos(clamp(dot / len, -1, 1)) 274 | if (u.x * v.y - u.y * v.x) < 0: 275 | result = -result 276 | 277 | let 278 | cp = vec2(q * radii.x * p.y / radii.y, -q * radii.y * p.x / radii.x) 279 | center = vec2( 280 | cos(radians) * cp.x - sin(radians) * cp.y + (at.x + to.x) / 2, 281 | sin(radians) * cp.x + cos(radians) * cp.y + (at.y + to.y) / 2 282 | ) 283 | theta = svgAngle(vec2(1, 0), vec2((p.x-cp.x) / radii.x, (p.y - cp.y) / radii.y)) 284 | 285 | var delta = svgAngle( 286 | vec2((p.x - cp.x) / radii.x, (p.y - cp.y) / radii.y), 287 | vec2((-p.x - cp.x) / radii.x, (-p.y - cp.y) / radii.y) 288 | ) 289 | delta = delta mod (PI * 2) 290 | 291 | if sweep and delta < 0: 292 | delta += 2 * PI 293 | elif not sweep and delta > 0: 294 | delta -= 2 * PI 295 | 296 | # Normalize the delta 297 | while delta > PI * 2: 298 | delta -= PI * 2 299 | while delta < -PI * 2: 300 | delta += PI * 2 301 | 302 | ArcParams( 303 | radii: radii, 304 | rotMat: rotate(-radians), 305 | center: center, 306 | theta: theta, 307 | delta: delta 308 | ) 309 | 310 | proc compute(arc: ArcParams, a: float32): Vec2 = 311 | result = vec2(cos(a) * arc.radii.x, sin(a) * arc.radii.y) 312 | result = arc.rotMat * result + arc.center 313 | 314 | let arc = endpointToCenterArcParams(at, radii, rotation, large, sweep, to) 315 | 316 | var 317 | t: float32 # Where we are at on the curve from [0, 1] 318 | step = 1.float32 # How far we want to try to move along the curve 319 | prev = at 320 | while t != 1: 321 | if step <= epsilon(float32): 322 | raise newException(TypographyError, "Unable to discretize arc") 323 | let 324 | aPrev = arc.theta + arc.delta * t 325 | a = arc.theta + arc.delta * (t + step) 326 | next = arc.compute(a) 327 | halfway = arc.compute(aPrev + (a - aPrev) / 2) 328 | midpoint = (prev + next) / 2 329 | error = (midpoint - halfway).lengthSq 330 | if error > errorMarginSq: 331 | let 332 | quarterway = arc.compute(aPrev + (a - aPrev) / 4) 333 | midpoint = (prev + halfway) / 2 334 | halfwayError = (midpoint - quarterway).lengthSq 335 | if halfwayError < errorMarginSq: 336 | shape.addSegment(prev, halfway) 337 | prev = halfway 338 | t += step / 2 339 | step = min(step / 2, 1 - t) # Assume next steps hould be the same size 340 | else: 341 | step = step / 4 # We know a half-step is too big 342 | else: 343 | shape.addSegment(prev, next) 344 | prev = next 345 | t += step 346 | step = min(step * 2, 1 - t) # Optimistically attempt larger steps 347 | 348 | let path = cast[PathShim](path) 349 | 350 | var i: int 351 | while i < path.commands.len: 352 | let kind = path.commands[i].PathCommandKind 353 | inc i 354 | 355 | case kind: 356 | of Move: 357 | if shape.len > 0: 358 | if closeSubpaths: 359 | shape.addSegment(at, start) 360 | result.add(shape) 361 | shape = newSeq[Vec2]() 362 | at.x = path.commands[i + 0] 363 | at.y = path.commands[i + 1] 364 | start = at 365 | 366 | of Line: 367 | let to = vec2(path.commands[i + 0], path.commands[i + 1]) 368 | shape.addSegment(at, to) 369 | at = to 370 | 371 | of HLine: 372 | let to = vec2(path.commands[i + 0], at.y) 373 | shape.addSegment(at, to) 374 | at = to 375 | 376 | of VLine: 377 | let to = vec2(at.x, path.commands[i + 0]) 378 | shape.addSegment(at, to) 379 | at = to 380 | 381 | of Cubic: 382 | let 383 | ctrl1 = vec2(path.commands[i + 0], path.commands[i + 1]) 384 | ctrl2 = vec2(path.commands[i + 2], path.commands[i + 3]) 385 | to = vec2(path.commands[i + 4], path.commands[i + 5]) 386 | shape.addCubic(at, ctrl1, ctrl2, to) 387 | at = to 388 | prevCtrl2 = ctrl2 389 | 390 | of SCubic: 391 | let 392 | ctrl2 = vec2(path.commands[i + 0], path.commands[i + 1]) 393 | to = vec2(path.commands[i + 2], path.commands[i + 3]) 394 | if prevCommandKind in {Cubic, SCubic, RCubic, RSCubic}: 395 | let ctrl1 = at * 2 - prevCtrl2 396 | shape.addCubic(at, ctrl1, ctrl2, to) 397 | else: 398 | shape.addCubic(at, at, ctrl2, to) 399 | at = to 400 | prevCtrl2 = ctrl2 401 | 402 | of Quad: 403 | let 404 | ctrl = vec2(path.commands[i + 0], path.commands[i + 1]) 405 | to = vec2(path.commands[i + 2], path.commands[i + 3]) 406 | shape.addQuadratic(at, ctrl, to) 407 | at = to 408 | prevCtrl = ctrl 409 | 410 | of TQuad: 411 | let 412 | to = vec2(path.commands[i + 0], path.commands[i + 1]) 413 | ctrl = 414 | if prevCommandKind in {Quad, TQuad, RQuad, RTQuad}: 415 | at * 2 - prevCtrl 416 | else: 417 | at 418 | shape.addQuadratic(at, ctrl, to) 419 | at = to 420 | prevCtrl = ctrl 421 | 422 | of Arc: 423 | let 424 | radii = vec2(path.commands[i + 0], path.commands[i + 1]) 425 | rotation = path.commands[i + 2] 426 | large = path.commands[i + 3] == 1 427 | sweep = path.commands[i + 4] == 1 428 | to = vec2(path.commands[i + 5], path.commands[i + 6]) 429 | shape.addArc(at, radii, rotation, large, sweep, to) 430 | at = to 431 | 432 | of RMove: 433 | if shape.len > 0: 434 | result.add(shape) 435 | shape = newSeq[Vec2]() 436 | at.x += path.commands[i + 0] 437 | at.y += path.commands[i + 1] 438 | start = at 439 | 440 | of RLine: 441 | let to = vec2(at.x + path.commands[i + 0], at.y + path.commands[i + 1]) 442 | shape.addSegment(at, to) 443 | at = to 444 | 445 | of RHLine: 446 | let to = vec2(at.x + path.commands[i + 0], at.y) 447 | shape.addSegment(at, to) 448 | at = to 449 | 450 | of RVLine: 451 | let to = vec2(at.x, at.y + path.commands[i + 0]) 452 | shape.addSegment(at, to) 453 | at = to 454 | 455 | of RCubic: 456 | let 457 | ctrl1 = vec2(at.x + path.commands[i + 0], at.y + path.commands[i + 1]) 458 | ctrl2 = vec2(at.x + path.commands[i + 2], at.y + path.commands[i + 3]) 459 | to = vec2(at.x + path.commands[i + 4], at.y + path.commands[i + 5]) 460 | shape.addCubic(at, ctrl1, ctrl2, to) 461 | at = to 462 | prevCtrl2 = ctrl2 463 | 464 | of RSCubic: 465 | let 466 | ctrl2 = vec2(at.x + path.commands[i + 0], at.y + path.commands[i + 1]) 467 | to = vec2(at.x + path.commands[i + 2], at.y + path.commands[i + 3]) 468 | ctrl1 = 469 | if prevCommandKind in {Cubic, SCubic, RCubic, RSCubic}: 470 | at * 2 - prevCtrl2 471 | else: 472 | at 473 | shape.addCubic(at, ctrl1, ctrl2, to) 474 | at = to 475 | prevCtrl2 = ctrl2 476 | 477 | of RQuad: 478 | let 479 | ctrl = vec2(at.x + path.commands[i + 0], at.y + path.commands[i + 1]) 480 | to = vec2(at.x + path.commands[i + 2], at.y + path.commands[i + 3]) 481 | shape.addQuadratic(at, ctrl, to) 482 | at = to 483 | prevCtrl = ctrl 484 | 485 | of RTQuad: 486 | let 487 | to = vec2(at.x + path.commands[i + 0], at.y + path.commands[i + 1]) 488 | ctrl = 489 | if prevCommandKind in {Quad, TQuad, RQuad, RTQuad}: 490 | at * 2 - prevCtrl 491 | else: 492 | at 493 | shape.addQuadratic(at, ctrl, to) 494 | at = to 495 | prevCtrl = ctrl 496 | 497 | of RArc: 498 | let 499 | radii = vec2(path.commands[i + 0], path.commands[i + 1]) 500 | rotation = path.commands[i + 2] 501 | large = path.commands[i + 3] == 1 502 | sweep = path.commands[i + 4] == 1 503 | to = vec2(at.x + path.commands[i + 5], at.y + path.commands[i + 6]) 504 | shape.addArc(at, radii, rotation, large, sweep, to) 505 | at = to 506 | 507 | of Close: 508 | if at != start: 509 | shape.addSegment(at, start) 510 | at = start 511 | if shape.len > 0: 512 | result.add(shape) 513 | shape = newSeq[Vec2]() 514 | 515 | i += kind.parameterCount() 516 | prevCommandKind = kind 517 | 518 | if shape.len > 0: 519 | if closeSubpaths: 520 | shape.addSegment(at, start) 521 | result.add(shape) 522 | 523 | proc commandsToShapes*(glyph: Glyph) = 524 | ## Converts SVG-like commands to shape made out of lines 525 | glyph.shapes = glyph.path.commandsToShapes(false, 1.0) 526 | for shape in glyph.shapes: 527 | glyph.segments.add(toSeq(shape.segments)) 528 | -------------------------------------------------------------------------------- /src/typography/layout.nim: -------------------------------------------------------------------------------- 1 | import bumpy, pixie, font, rasterizer, tables, unicode, vmath 2 | 3 | const 4 | normalLineHeight* = 0 ## Default line height of font.size * 1.2 5 | 6 | type 7 | Font = font.Font 8 | 9 | Span* = object 10 | ## Represents a run of litter of same size and font. 11 | font: Font 12 | fontSize: float32 13 | # lineHeight: float32 14 | # tracking: float32 15 | text: string 16 | 17 | GlyphPosition* = object 18 | ## Represents a glyph position after typesetting. 19 | font*: Font 20 | fontSize*: float32 21 | subPixelShift*: float32 22 | rect*: Rect # Where to draw the image character. 23 | selectRect*: Rect # Were to draw or hit selection. 24 | character*: string 25 | rune*: Rune 26 | count*: int 27 | index*: int 28 | 29 | HAlignMode* = enum 30 | ## Horizontal alignment mode. 31 | Left 32 | Center 33 | Right 34 | 35 | VAlignMode* = enum 36 | ## Vertical alignment mode. 37 | Top 38 | Middle 39 | Bottom 40 | 41 | TextCase* = enum 42 | tcNormal 43 | tcUpper 44 | tcLower 45 | tcTitle 46 | # tcSmallCaps 47 | # tcSmallCapsForced 48 | 49 | proc convertTextCase*(s: string, textCase: TextCase): string = 50 | case textCase: 51 | of tcNormal: s 52 | of tcUpper: s.toUpper() 53 | of tcLower: s.toLower() 54 | of tcTitle: s.title() 55 | 56 | proc kerningAdjustment*(font: Font, prev, c: string): float32 = 57 | ## Get Kerning Adjustment between two letters. 58 | if prev != "": 59 | var key = (prev, c) 60 | if font.typeface.kerning.hasKey(key): 61 | var kerning = font.typeface.kerning[key] 62 | return kerning 63 | 64 | proc canWrap(rune: Rune): bool = 65 | if rune == Rune(32): return true # Early return for ascii space. 66 | if rune.isWhiteSpace(): return true 67 | if not rune.isAlpha(): return true 68 | 69 | proc typeset*( 70 | font: Font, 71 | runes: seq[Rune], 72 | pos: Vec2 = vec2(0, 0), 73 | size: Vec2 = vec2(0, 0), 74 | hAlign: HAlignMode = Left, 75 | vAlign: VAlignMode = Top, 76 | clip = true, 77 | wrap = true, 78 | kern = true, 79 | textCase = tcNormal, 80 | tabWidth: float32 = 0.0, 81 | boundsMin: var Vec2, 82 | boundsMax: var Vec2 83 | ): seq[GlyphPosition] = 84 | ## Typeset runes and return glyph positions that is ready to draw. 85 | 86 | assert font.size != 0 87 | assert font.typeface != nil 88 | assert font.typeface.unitsPerEm != 0 89 | 90 | var 91 | at = pos 92 | lineStart = pos.x 93 | prev = "" 94 | ## Figure out why some times the scale is ignored this way: 95 | #scale = font.size / (font.ascent - font.descent) 96 | glyphCount = 0 97 | tabWidth = tabWidth 98 | 99 | if tabWidth == 0.0: 100 | tabWidth = font.size * 4 101 | 102 | var 103 | strIndex = 0 104 | glyphIndex = 0 105 | lastCanWrap = 0 106 | lineHeight = font.lineHeight 107 | 108 | if lineHeight == normalLineHeight: 109 | lineHeight = font.size 110 | 111 | let selectionHeight = max(font.size, lineHeight) 112 | 113 | at.y += font.baseline 114 | 115 | for rune in runes: 116 | var c = $rune 117 | if rune == Rune(10): # New line "\n". 118 | # Add special small width glyph on this line. 119 | var selectRect = rect( 120 | floor(at.x), 121 | floor(at.y) - font.baseline, 122 | font.typeface.glyphs[" "].advance * font.scale, 123 | selectionHeight 124 | ) 125 | result.add GlyphPosition( 126 | font: font, 127 | fontSize: font.size, 128 | subPixelShift: 0, 129 | rect: rect(0, 0, 0, 0), 130 | selectRect: selectRect, 131 | rune: rune, 132 | character: c, 133 | count: glyphCount, 134 | index: strIndex 135 | ) 136 | prev = c 137 | inc glyphCount 138 | strIndex += c.len 139 | 140 | at.x = lineStart 141 | at.y += lineHeight 142 | continue 143 | elif rune == Rune(9): # tab \t 144 | at.x = ceil(at.x / tabWidth) * tabWidth 145 | continue 146 | 147 | if canWrap(rune): 148 | lastCanWrap = glyphIndex + 1 149 | 150 | if c notin font.typeface.glyphs: 151 | # TODO: Make missing glyphs work better. 152 | c = " " # If glyph is missing use space for now. 153 | if c notin font.typeface.glyphs: 154 | ## Space is missing!? 155 | continue 156 | 157 | var glyph = font.typeface.glyphs[c] 158 | if kern: 159 | at.x += font.kerningAdjustment(prev, c) * font.scale 160 | 161 | let q = 162 | if font.size < 20: 0.1 163 | elif font.size < 25: 0.2 164 | elif font.size < 30: 0.5 165 | else: 1.0 166 | var subPixelShift = quantize(at.x - floor(at.x), q) 167 | var glyphPos = vec2(floor(at.x), floor(at.y)) 168 | var glyphSize = font.getGlyphSize(glyph) 169 | 170 | if rune == Rune(32): 171 | glyphSize.x = glyph.advance * font.scale 172 | 173 | if glyphSize.x != 0 and glyphSize.y != 0: 174 | # Does it need to wrap? 175 | if wrap and size.x != 0 and at.x - pos.x + glyphSize.x > size.x: 176 | # Wrap to next line. 177 | let goBack = lastCanWrap - glyphIndex 178 | if lastCanWrap != -1 and goBack < 0: 179 | lastCanWrap = -1 180 | at.y += lineHeight 181 | if clip and size.y != 0 and at.y - pos.y > size.y: 182 | # Delete glyphs that would wrap into next line 183 | # that is clipped. 184 | result.setLen(result.len + goBack) 185 | return 186 | 187 | # Wrap glyphs on prev line down to next line. 188 | let shift = result[result.len + goBack].rect.x - pos.x 189 | for i in result.len + goBack ..< result.len: 190 | result[i].rect.x -= shift 191 | result[i].rect.y += lineHeight 192 | result[i].selectRect.x -= shift 193 | result[i].selectRect.y += lineHeight 194 | 195 | at.x -= shift 196 | else: 197 | at.y += lineHeight 198 | at.x = lineStart 199 | 200 | glyphPos = vec2(floor(at.x), floor(at.y)) 201 | 202 | if clip and size.y != 0 and at.y - pos.y > size.y: 203 | # Reached the bottom of the area, clip. 204 | return 205 | 206 | var selectRect = rect( 207 | floor(at.x), 208 | floor(at.y) - font.baseline, 209 | glyphSize.x, 210 | selectionHeight 211 | ) 212 | 213 | if result.len > 0: 214 | # Adjust selection rect width to next character 215 | if result[^1].selectRect.y == selectRect.y: 216 | result[^1].selectRect.w = floor(at.x) - result[^1].selectRect.x 217 | 218 | result.add GlyphPosition( 219 | font: font, 220 | fontSize: font.size, 221 | subPixelShift: subPixelShift, 222 | rect: rect(glyphPos, glyphSize), 223 | selectRect: selectRect, 224 | character: c, 225 | rune: rune, 226 | count: glyphCount, 227 | index: strIndex 228 | ) 229 | if glyphCount == 0: 230 | # First glyph. 231 | boundsMax.x = selectRect.x + selectRect.w 232 | boundsMin.x = selectRect.x 233 | boundsMax.y = selectRect.y + selectRect.h 234 | boundsMin.y = selectRect.y 235 | else: 236 | boundsMax.x = max(boundsMax.x, selectRect.x + selectRect.w) 237 | boundsMin.x = min(boundsMin.x, selectRect.x) 238 | boundsMax.y = max(boundsMax.y, selectRect.y + selectRect.h) 239 | boundsMin.y = min(boundsMin.y, selectRect.y) 240 | 241 | inc glyphIndex 242 | at.x += glyph.advance * font.scale 243 | prev = c 244 | inc glyphCount 245 | strIndex += c.len 246 | 247 | ## Shifts layout by alignMode. 248 | if result.len == 0: return 249 | 250 | let boundsSize = boundsMax - boundsMin 251 | 252 | if hAlign == Right: 253 | let offset = floor(size.x - boundsSize.x) 254 | for pos in result.mitems: 255 | pos.rect.x += offset 256 | pos.selectRect.x += offset 257 | 258 | if hAlign == Center: 259 | let offset = floor((size.x - boundsSize.x) / 2.0) 260 | for pos in result.mitems: 261 | pos.rect.x += offset 262 | pos.selectRect.x += offset 263 | 264 | if vAlign == Bottom: 265 | let offset = floor(size.y - boundsSize.y) 266 | for pos in result.mitems: 267 | pos.rect.y += offset 268 | pos.selectRect.y += offset 269 | 270 | if vAlign == Middle: 271 | let offset = floor((size.y - boundsSize.y) / 2.0) 272 | for pos in result.mitems: 273 | pos.rect.y += offset 274 | pos.selectRect.y += offset 275 | 276 | proc typeset*( 277 | font: Font, 278 | text: string, 279 | pos: Vec2 = vec2(0, 0), 280 | size: Vec2 = vec2(0, 0), 281 | hAlign: HAlignMode = Left, 282 | vAlign: VAlignMode = Top, 283 | clip = true, 284 | wrap = true, 285 | kern = true, 286 | textCase = tcNormal, 287 | tabWidth: float32 = 0.0 288 | ): seq[GlyphPosition] = 289 | ## Typeset string and return glyph positions that is ready to draw. 290 | var 291 | ignoreBoundsMin: Vec2 292 | ignoreBoundsMax: Vec2 293 | typeset( 294 | font, toRunes(convertTextCase(text, textCase)), pos, size, 295 | hAlign, vAlign, 296 | clip, wrap, kern, textCase, 297 | tabWidth, 298 | ignoreBoundsMin, ignoreBoundsMax 299 | ) 300 | 301 | proc drawText*(image: var Image, layout: seq[GlyphPosition]) = 302 | ## Draws layout. 303 | for pos in layout: 304 | var font = pos.font 305 | if pos.character in font.typeface.glyphs: 306 | var glyph = font.typeface.glyphs[pos.character] 307 | var glyphOffset: Vec2 308 | let img = font.getGlyphImage( 309 | glyph, 310 | glyphOffset, 311 | subPixelShift = pos.subPixelShift 312 | ) 313 | image.draw( 314 | img, 315 | translate(vec2(pos.rect.x + glyphOffset.x, pos.rect.y + glyphOffset.y)) 316 | ) 317 | 318 | proc drawText*(font: Font, image: var Image, pos: Vec2, text: string) = 319 | ## Draw text string 320 | var layout = font.typeset(text, pos) 321 | image.drawText(layout) 322 | 323 | proc getSelection*(layout: seq[GlyphPosition], start, stop: int): seq[Rect] = 324 | ## Given a layout gives selection from start to stop in glyph positions. 325 | ## If start == stop returns []. 326 | if start == stop: 327 | return 328 | for g in layout: 329 | if g.count >= start and g.count < stop: 330 | if result.len > 0: 331 | let onSameLine = result[^1].y == g.selectRect.y and result[^1].h == g.selectRect.h 332 | let notTooFar = g.selectRect.x - result[^1].x < result[^1].w * 2 333 | if onSameLine and notTooFar: 334 | result[^1].w = g.selectRect.x - result[^1].x + g.selectRect.w 335 | continue 336 | result.add g.selectRect 337 | 338 | proc pickGlyphAt*(layout: seq[GlyphPosition], pos: Vec2): GlyphPosition = 339 | ## Given X,Y coordinate, return the GlyphPosition picked. 340 | ## If direct click not happened finds closest to the right. 341 | var minG: GlyphPosition 342 | var minDist = -1.0 343 | for i, g in layout: 344 | if g.selectRect.y <= pos.y and pos.y < g.selectRect.y + g.selectRect.h: 345 | # on same line 346 | let dist = abs(pos.x - (g.selectRect.x)) 347 | # closet character 348 | if minDist < 0 or dist < minDist: 349 | # min distance here 350 | minDist = dist 351 | minG = g 352 | return minG 353 | 354 | proc textBounds*(layout: seq[GlyphPosition]): Vec2 = 355 | ## Given a layout, return the bounding rectangle. 356 | ## You can use this to get text width or height. 357 | for i, g in layout: 358 | result.x = max(result.x, g.selectRect.x + g.selectRect.w) 359 | result.y = max(result.y, g.selectRect.y + g.selectRect.h) 360 | 361 | proc textBounds*(font: Font, text: string): Vec2 = 362 | ## Given a font and text, return the bounding rectangle. 363 | ## You can use this to get text width or height. 364 | var layout = font.typeset(text) 365 | return layout.textBounds() 366 | -------------------------------------------------------------------------------- /src/typography/linebreak.nim: -------------------------------------------------------------------------------- 1 | import strutils, tables, unicode 2 | 3 | type 4 | BreakRegion = object 5 | start: int 6 | stop: int 7 | code: BreakCode 8 | 9 | BreakCode {.pure.} = enum 10 | MandatoryBreak # table["BK"] = BreakCode.Cause a line break (after) 11 | CarriageReturn # CR Cause a line break (after), except between CR and LF 12 | LineFeed # LF Cause a line break (after) 13 | CombiningMark # CM Prohibit a line break between the character and the preceding character 14 | NextLine # NL Cause a line break (after) 15 | Surrogate # SG Do not occur in well-formed text 16 | WordJoiner # WJ Prohibit line breaks before and after 17 | ZeroWidthSpace # ZW Provide a break opportunity 18 | NonBreaking # GL Prohibit line breaks before and after 19 | Space # SP Enable indirect line breaks 20 | ZeroWidthJoiner # ZWJ Prohibit line breaks within joiner sequences 21 | ## Break Opportunities 22 | BreakOpportunityBeforeAndAfter # B2 Provide a line break opportunity before and after the character 23 | BreakAfter # BA Generally provide a line break opportunity after the character 24 | BreakBefore # BB Generally provide a line break opportunity before the character 25 | Hyphen # HY Provide a line break opportunity after the character, except in numeric context 26 | ContingentBreakOpportunity # CB Provide a line break opportunity contingent on additional information 27 | ## Characters Prohibiting Certain Breaks 28 | ClosePunctuation # CL Prohibit line breaks before 29 | CloseParenthesis # CP Prohibit line breaks before 30 | ExclamationInterrogation # EX Prohibit line breaks before 31 | Inseparable # IN Allow only indirect line breaks between pairs 32 | Nonstarter # NS Allow only indirect line breaks before 33 | OpenPunctuation # OP Prohibit line breaks after 34 | Quotation # QU Act like they are both opening and closing 35 | ## Numeric Context 36 | InfixNumericSeparator # IS Prevent breaks after any and before numeric 37 | Numeric # NU Form numeric expressions for line breaking purposes 38 | PostfixNumeric # PO Do not break following a numeric expression 39 | PrefixNumeric # PR Do not break in front of a numeric expression 40 | SymbolsAllowingBreakAfter # SY Prevent a break before, and allow a break after 41 | ## Other Characters 42 | Ambiguous # (Alphabetic or Ideographic) AI Act like AL when the resolved EAW is N; otherwise, act as ID 43 | Alphabetic # AL Are alphabetic characters or symbols that are used with alphabetic characters 44 | ConditionalJapaneseStarter # CJ Treat as NS or ID for strict or normal breaking. 45 | EmojiBase # EB Do not break from following Emoji Modifier 46 | EmojiModifier # EM Do not break from preceding Emoji Base 47 | HangulLVSyllable # H2 Form Korean syllable blocks 48 | HangulLVTSyllable # H3 Form Korean syllable blocks 49 | HebrewLetter # HL Do not break around a following hyphen; otherwise act as Alphabetic 50 | Ideographic # ID Break before or after, except in some numeric context 51 | HangulLJamo # JL Form Korean syllable blocks 52 | HangulVJamo # JV Form Korean syllable blocks 53 | HangulTJamo # JT Form Korean syllable blocks 54 | RegionalIndicator # RI Keep pairs together. For pairs, break before and after other classes 55 | ComplexContextDependent # South East Asian SA Provide a line break opportunity contingent on additional, language-specific context analysis 56 | Unknown # XX Have as yet unknown line breaking behavior or unassigned code positions 57 | 58 | var table = newTable[string, BreakCode]() 59 | table["BK"] = BreakCode.MandatoryBreak 60 | table["CR"] = BreakCode.CarriageReturn 61 | table["LF"] = BreakCode.LineFeed 62 | table["CM"] = BreakCode.CombiningMark 63 | table["NL"] = BreakCode.NextLine 64 | table["SG"] = BreakCode.Surrogate 65 | table["WJ"] = BreakCode.WordJoiner 66 | table["ZW"] = BreakCode.ZeroWidthSpace 67 | table["GL"] = BreakCode.NonBreaking 68 | table["SP"] = BreakCode.Space 69 | table["ZWJ"] = BreakCode.ZeroWidthJoiner 70 | table["B2"] = BreakCode.BreakOpportunityBeforeAndAfter 71 | table["BA"] = BreakCode.BreakAfter 72 | table["BB"] = BreakCode.BreakBefore 73 | table["HY"] = BreakCode.Hyphen 74 | table["CB"] = BreakCode.ContingentBreakOpportunity 75 | table["CL"] = BreakCode.ClosePunctuation 76 | table["CP"] = BreakCode.CloseParenthesis 77 | table["EX"] = BreakCode.ExclamationInterrogation 78 | table["IN"] = BreakCode.Inseparable 79 | table["NS"] = BreakCode.Nonstarter 80 | table["OP"] = BreakCode.OpenPunctuation 81 | table["QU"] = BreakCode.Quotation 82 | table["IS"] = BreakCode.InfixNumericSeparator 83 | table["NU"] = BreakCode.Numeric 84 | table["PO"] = BreakCode.PostfixNumeric 85 | table["PR"] = BreakCode.PrefixNumeric 86 | table["SY"] = BreakCode.SymbolsAllowingBreakAfter 87 | table["AI"] = BreakCode.Ambiguous 88 | table["AL"] = BreakCode.Alphabetic 89 | table["CJ"] = BreakCode.ConditionalJapaneseStarter 90 | table["EB"] = BreakCode.EmojiBase 91 | table["EM"] = BreakCode.EmojiModifier 92 | table["H2"] = BreakCode.HangulLVSyllable 93 | table["H3"] = BreakCode.HangulLVTSyllable 94 | table["HL"] = BreakCode.HebrewLetter 95 | table["ID"] = BreakCode.Ideographic 96 | table["JL"] = BreakCode.HangulLJamo 97 | table["JV"] = BreakCode.HangulVJamo 98 | table["JT"] = BreakCode.HangulTJamo 99 | table["RI"] = BreakCode.RegionalIndicator 100 | table["SA"] = BreakCode.ComplexContextDependent 101 | table["XX"] = BreakCode.Unknown 102 | 103 | var breakRegions = newSeq[BreakRegion]() 104 | 105 | const lineBreakText = staticRead("linebreak.txt") 106 | for line in lineBreakText.splitLines: 107 | var line = line 108 | let index = line.find('#') 109 | if index >= 0: 110 | line = line[0..= int(rune) and b.stop <= int(rune): 129 | return b.code 130 | return BreakCode.Unknown 131 | -------------------------------------------------------------------------------- /src/typography/opentype/types.nim: -------------------------------------------------------------------------------- 1 | import tables 2 | 3 | type 4 | HeadTable* = ref object 5 | majorVersion*: uint16 ## Major version number of the font header table — set to 1. 6 | minorVersion*: uint16 ## Minor version number of the font header table — set to 0. 7 | fontRevision*: float32 ## Set by font manufacturer. 8 | checkSumAdjustment*: uint32 ## Ignored. 9 | magicNumber*: uint32 ## Set to 0x5F0F3CF5. 10 | flags*: uint16 11 | unitsPerEm*: uint16 ## Set to a value from 16 to 16384. 12 | created*: float64 13 | modified*: float64 14 | xMin*: int16 ## For all glyph bounding boxes. 15 | yMin*: int16 ## For all glyph bounding boxes. 16 | xMax*: int16 ## For all glyph bounding boxes. 17 | yMax*: int16 ## For all glyph bounding boxes. 18 | macStyle*: uint16 ## Bit flags. 19 | lowestRecPPEM*: uint16 ## Smallest readable size in pixels. 20 | fontDirectionHint*: int16 ## Deprecated. 21 | indexToLocFormat*: int16 ## 0 for short offsets (Offset16), 1 for long (Offset32). 22 | glyphDataFormat*: int16 ## 0 for current format. 23 | 24 | NameTable* = ref object 25 | format*: uint16 26 | count*: uint16 27 | stringOffset*: uint16 28 | nameRecords*: seq[NameRecord] 29 | 30 | NameRecord* = object 31 | platformID*: uint16 # Platform ID. 32 | encodingID*: uint16 # Platform-specific encoding ID. 33 | languageID*: uint16 # Language ID. 34 | nameID*: uint16 # Name ID. 35 | name*: NameTableNames 36 | length*: uint16 # String length (in bytes). 37 | offset*: uint16 # String offset from start of storage area (in bytes). 38 | text*: string 39 | 40 | NameTableNames* = enum 41 | ntnCopyright, # 0 42 | ntnFontFamily, # 1 43 | ntnFontSubfamily, # 2 44 | ntnUniqueID, # 3 45 | ntnFullName, # 4 46 | ntnVersion, # 5 47 | ntnPostScriptName, # 6 48 | ntnTrademark, # 7 49 | ntnManufacturer, # 8 50 | ntnDesigner, # 9 51 | ntnDescription, # 10 52 | ntnManufacturerURL, # 11 53 | ntnDesignerURL, # 12 54 | ntnLicense, # 13 55 | ntnLicenseURL, # 14 56 | ntnReserved, # 15 57 | ntnPreferredFamily, # 16 58 | ntnPreferredSubfamily, # 17 59 | ntnCompatibleFullName, # 18 60 | ntnSampleText, # 19 61 | ntnPostScriptFindFontName, # 20 62 | ntnWwsFamily, # 21 63 | ntnWwsSubfamily # 22 64 | 65 | MaxpTable* = ref object 66 | version*: float32 ## 0x00010000 for version 1.0. 67 | numGlyphs*: uint16 ## The number of glyphs in the otf. 68 | maxPoints*: uint16 ## Maximum points in a non-composite glyph. 69 | maxContours*: uint16 ## Maximum contours in a non-composite glyph. 70 | maxCompositePoints*: uint16 ## Maximum points in a composite glyph. 71 | maxCompositeContours*: uint16 ## Maximum contours in a composite glyph. 72 | maxZones*: uint16 ## 1 if instructions do not use the twilight zone (Z0), or 2 if instructions do use Z0; should be set to 2 in most cases. 73 | maxTwilightPoints*: uint16 ## Maximum points used in Z0. 74 | maxStorage*: uint16 ## Number of Storage Area locations. 75 | maxFunctionDefs*: uint16 ## Number of FDEFs, equal to the highest function number + 1. 76 | maxInstructionDefs*: uint16 ## Number of IDEFs. 77 | maxStackElements*: uint16 ## Maximum stack depth across Font Program ('fpgm' table), CVT Program ('prep' table) and all glyph instructions (in the 'glyf' table). 78 | maxSizeOfInstructions*: uint16 ## Maximum byte count for glyph instructions. 79 | maxComponentElements*: uint16 ## Maximum number of components referenced at “top level” for any composite glyph. 80 | maxComponentDepth*: uint16 ## Maximum levels of recursion; 1 for simple components. 81 | 82 | OS2Table* = ref object 83 | version*: uint16 84 | xAvgCharWidth*: int16 85 | usWeightClass*: uint16 86 | usWidthClass*: uint16 87 | fsType*: uint16 88 | ySubscriptXSize*: int16 89 | ySubscriptYSize*: int16 90 | ySubscriptXOffset*: int16 91 | ySubscriptYOffset*: int16 92 | ySuperscriptXSize*: int16 93 | ySuperscriptYSize*: int16 94 | ySuperscriptXOffset*: int16 95 | ySuperscriptYOffset*: int16 96 | yStrikeoutSize*: int16 97 | yStrikeoutPosition*: int16 98 | sFamilyClass*: int16 99 | panose*: array[10, uint8] 100 | ulUnicodeRange1*: uint32 101 | ulUnicodeRange2*: uint32 102 | ulUnicodeRange3*: uint32 103 | ulUnicodeRange4*: uint32 104 | achVendID*: string 105 | fsSelection*: uint16 106 | usFirstCharIndex*: uint16 107 | usLastCharIndex*: uint16 108 | sTypoAscender*: int16 109 | sTypoDescender*: int16 110 | sTypoLineGap*: int16 111 | usWinAscent*: uint16 112 | usWinDescent*: uint16 113 | ulCodePageRange1*: uint32 114 | ulCodePageRange2*: uint32 115 | sxHeight*: int16 116 | sCapHeight*: int16 117 | usDefaultChar*: uint16 118 | usBreakChar*: uint16 119 | usMaxContext*: uint16 120 | usLowerOpticalPointSize*: uint16 121 | usUpperOpticalPointSize*: uint16 122 | 123 | LocaTable* = ref object 124 | offsets*: seq[uint32] 125 | 126 | HheaTable* = ref object 127 | ## Horizontal Header Table. 128 | majorVersion*: uint16 ## Major version number of the horizontal header table — set to 1. 129 | minorVersion*: uint16 ## Minor version number of the horizontal header table — set to 0. 130 | ascender*: int16 ## Typographic ascent (Distance from baseline of highest ascender). 131 | descender*: int16 ## Typographic descent (Distance from baseline of lowest descender). 132 | lineGap*: int16 ## Typographic line gap. Negative LineGap values are treated as zero in some legacy platform implementations. 133 | advanceWidthMax*: uint16 ## Maximum advance width value in 'hmtx' table. 134 | minLeftSideBearing*: int16 ## Minimum left sidebearing value in 'hmtx' table. 135 | minRightSideBearing*: int16 ## Minimum right sidebearing value; calculated as Min(aw - lsb - (xMax - xMin)). 136 | xMaxExtent*: int16 ## Max(lsb + (xMax - xMin)). 137 | caretSlopeRise*: int16 ## Used to calculate the slope of the cursor (rise/run); 1 for vertical. 138 | caretSlopeRun*: int16 ## 0 for vertical. 139 | caretOffset*: int16 ## The amount by which a slanted highlight on a glyph needs to be shifted to produce the best appearance. Set to 0 for non-slanted fonts 140 | metricDataFormat*: int16 ## 0 for current format. 141 | numberOfHMetrics*: uint16 ## Number of hMetric entries in 'hmtx' table. 142 | 143 | HmtxTable* = ref object 144 | ## Horizontal Metrics Table 145 | hMetrics*: seq[LongHorMetricRecrod] 146 | leftSideBearings*: seq[int16] 147 | 148 | LongHorMetricRecrod* = object 149 | advanceWidth*: uint16 ## Advance width, in font design units. 150 | lsb*: int16 ## Glyph left side bearing, in font design units. 151 | 152 | KernTable* = ref object 153 | version*: uint16 ## Table version number (0) 154 | nTables*: uint16 ## Number of subtables in the kerning table. 155 | subTables*: seq[KernSubTable] 156 | 157 | KernSubTable* = object 158 | version*: uint16 159 | length*: uint16 160 | coverage*: uint16 161 | 162 | numPairs*: uint16 163 | searchRange*: uint16 164 | entrySelector*: uint16 165 | rangeShift*: uint16 166 | 167 | kerningPairs*: seq[KerningPair] 168 | 169 | KerningPair* = object 170 | left*: uint16 171 | right*: uint16 172 | value*: int16 173 | 174 | CmapTable* = ref object 175 | version*: uint16 ## Table version number (0). 176 | numTables*: uint16 ## Number of encoding tables that follow. 177 | encodingRecords*: seq[EncodingRecord] 178 | glyphIndexMap*: Table[int, int] 179 | 180 | EncodingRecord* = object 181 | platformID*: uint16 ## Platform ID. 182 | encodingID*: uint16 ## Platform-specific encoding ID. 183 | offset*: uint32 ## Byte offset from beginning of table to the subtable for this encoding. 184 | 185 | SegmentMapping* = ref object 186 | format*: uint16 ## Format number is set to 4. 187 | length*: uint16 ## This is the length in bytes of the subtable. 188 | language*: uint16 ## For requirements on use of the language field, see “Use of the language field in 'cmap' subtables” in this document. 189 | segCountX2*: uint16 ## 2 × segCount. 190 | searchRange*: uint16 ## 2 × (2**floor(log2(segCount))) 191 | entrySelector*: uint16 ## log2(searchRange/2) 192 | rangeShift*: uint16 ## 2 × segCount - searchRange 193 | endCode*: seq[uint16] ## [segCount] End characterCode for each segment, last=0xFFFF. 194 | reservedPad*: uint16 ## Set to 0. 195 | startCode*: seq[uint16] ## [segCount] Start character code for each segment. 196 | idDelta*: seq[uint16] ## [segCount] Delta for all character codes in segment. 197 | idRangeOffset*: seq[uint16] ## [segCount] Offsets into glyphIdArray or 0 198 | glyphIdArray*: seq[uint16] ## Glyph index array (arbitrary length) 199 | 200 | GlyfTable* = ref object 201 | offsets*: seq[int] 202 | 203 | # GlyphRecord = ref object 204 | # numberOfContours: int16 ## If the number of contours is greater than or equal to zero, this is a simple glyph. If negative, this is a composite glyph — the value -1 should be used for composite glyphs. 205 | # xMin: int16 ## Minimum x for coordinate data. 206 | # yMin: int16 ## Minimum y for coordinate data. 207 | # xMax: int16 ## Maximum x for coordinate data. 208 | # yMax: int16 ## Maximum y for coordinate data. 209 | 210 | # isNull: bool ## if glyph occupies 0 bytes 211 | # isComposite: bool ## if numberOfContours is -1 212 | # path: seq[PathCommand] 213 | 214 | TtfCoordinate* = object 215 | x*: float32 216 | y*: float32 217 | isOnCurve*: bool 218 | 219 | Chunk* = object 220 | tag*: string 221 | checkSum*: uint32 222 | offset*: uint32 223 | length*: uint32 224 | 225 | OtfFont* = ref object 226 | buf*: string 227 | version*: uint32 228 | numTables*: uint16 229 | searchRange*: uint16 230 | entrySelector*: uint16 231 | rangeShift*: uint16 232 | 233 | chunks*: Table[string, Chunk] 234 | 235 | head*: HeadTable 236 | name*: NameTable 237 | maxp*: MaxpTable 238 | os2*: OS2Table 239 | loca*: LocaTable 240 | hhea*: HheaTable 241 | hmtx*: HmtxTable 242 | kern*: KernTable 243 | cmap*: CmapTable 244 | glyf*: GlyfTable 245 | -------------------------------------------------------------------------------- /src/typography/rasterizer.nim: -------------------------------------------------------------------------------- 1 | import algorithm, bumpy, chroma, pixie, font, tables, vmath, opentype/parser 2 | 3 | type Font = font.Font 4 | 5 | proc makeReady*(glyph: Glyph, font: Font) = 6 | ## Make sure the glyph is ready to render 7 | var typeface = font.typeface 8 | if glyph.ready: 9 | return 10 | 11 | glyph.path = newPath() 12 | 13 | if typeface.otf != nil and not glyph.isEmpty: 14 | glyph.parseGlyph(font) 15 | if glyph.pathStr.len > 0: 16 | glyph.glyphPathToCommands() 17 | if glyph.segments.len == 0: 18 | glyph.commandsToShapes() 19 | 20 | if glyph.segments.len > 0 and glyph.segments[0].len > 0: 21 | glyph.bboxMin = glyph.segments[0][0].at 22 | glyph.bboxMax = glyph.segments[0][0].at 23 | for segments in glyph.segments: 24 | for s in segments: 25 | var at = s.at 26 | var to = s.to 27 | 28 | if at.x < glyph.bboxMin.x: glyph.bboxMin.x = at.x 29 | if at.y < glyph.bboxMin.y: glyph.bboxMin.y = at.y 30 | if at.x > glyph.bboxMax.x: glyph.bboxMax.x = at.x 31 | if at.y > glyph.bboxMax.y: glyph.bboxMax.y = at.y 32 | 33 | if to.x < glyph.bboxMin.x: glyph.bboxMin.x = to.x 34 | if to.y < glyph.bboxMin.y: glyph.bboxMin.y = to.y 35 | if to.x > glyph.bboxMax.x: glyph.bboxMax.x = to.x 36 | if to.y > glyph.bboxMax.y: glyph.bboxMax.y = to.y 37 | else: 38 | glyph.isEmpty = true 39 | 40 | glyph.ready = true 41 | 42 | proc getGlyphSize*(font: Font, glyph: Glyph): Vec2 = 43 | glyph.makeReady(font) 44 | var 45 | tx = floor(glyph.bboxMin.x * font.scale) 46 | ty = floor(glyph.bboxMin.y * font.scale) 47 | w = ceil(glyph.bboxMax.x * font.scale) - tx + 1 48 | h = ceil(glyph.bboxMax.y * font.scale) - ty + 1 49 | vec2(float32 w, float32 h) 50 | 51 | proc getGlyphImage*( 52 | font: Font, 53 | glyph: Glyph, 54 | glyphOffset: var Vec2, 55 | quality = 4, 56 | subPixelShift: float32 = 0.0, 57 | ): Image = 58 | ## Get image for this glyph 59 | let white = ColorRgba(r: 255, g: 255, b: 255, a: 255) 60 | 61 | let 62 | size = getGlyphSize(font, glyph) 63 | w = max(int(size.x), 0) 64 | h = max(int(size.y), 0) 65 | tx = floor(glyph.bboxMin.x * font.scale) 66 | ty = floor(glyph.bboxMin.y * font.scale) 67 | origin = vec2(tx, ty) 68 | 69 | if w == 0 or h == 0: 70 | return newImage(1, 1) 71 | 72 | result = newImage(w, h) 73 | 74 | glyphOffset.x = origin.x 75 | glyphOffset.y = -float32(h) - origin.y 76 | 77 | proc trans(v: Vec2): Vec2 = (v + origin) / font.scale 78 | 79 | const ep = 0.0001 * PI 80 | 81 | proc scanLineHits( 82 | shapes: seq[seq[Segment]], 83 | hits: var seq[(float32, bool)], 84 | y: int, 85 | shiftY: float32 86 | ) = 87 | hits.setLen(0) 88 | var yLine = (float32(y) + ep) + shiftY 89 | var scan = Line(a: vec2(-10000, yLine), b: vec2(10000, yLine)) 90 | 91 | scan.a = trans(scan.a) 92 | scan.b = trans(scan.b) 93 | 94 | for shape in shapes: 95 | for line in shape: 96 | var line2 = line 97 | if line2.at.y > line2.to.y: # Sort order doesn't actually matter 98 | swap(line2.at, line2.to) 99 | # Lines often connect and we need them to not share starts and ends 100 | var at: Vec2 101 | if line2.intersects(scan, at) and line2.to != at: 102 | let winding = line.at.y > line.to.y 103 | hits.add((at.x * font.scale - origin.x + subPixelShift, winding)) 104 | 105 | hits.sort(proc(a, b: (float32, bool)): int = cmp(a[0], b[0])) 106 | 107 | var hits: seq[(float32, bool)] 108 | 109 | if quality == 0: 110 | for y in 0 ..< result.height: 111 | glyph.segments.scanLineHits(hits, y, 0) 112 | if hits.len == 0: 113 | continue 114 | var 115 | pen: int16 = 0 116 | curHit = 0 117 | for x in 0 ..< result.width: 118 | while true: 119 | if curHit >= hits.len: 120 | break 121 | if x != hits[curHit][0].int: 122 | break 123 | let winding = hits[curHit][1] 124 | if winding == false: 125 | pen += 1 126 | else: 127 | pen -= 1 128 | inc curHit 129 | if pen != 0: 130 | result[x, h-y-1] = white 131 | else: 132 | var alphas = newSeq[float32](result.width) 133 | for y in 0 ..< result.height: 134 | for x in 0 ..< result.width: 135 | alphas[x] = 0 136 | for m in 0 ..< quality: 137 | glyph.segments.scanLineHits(hits, y, float32(m)/float32(quality)) 138 | if hits.len == 0: 139 | continue 140 | var 141 | penFill = 0.0 142 | curHit = 0 143 | for x in 0 ..< result.width: 144 | var penEdge = penFill 145 | while true: 146 | if curHit >= hits.len: 147 | break 148 | if x != hits[curHit][0].int: 149 | break 150 | let cover = hits[curHit][0] - x.float32 151 | let winding = hits[curHit][1] 152 | if winding == false: 153 | penFill += 1.0 154 | penEdge += 1.0 - cover 155 | else: 156 | penFill -= 1.0 157 | penEdge -= 1.0 - cover 158 | inc curHit 159 | alphas[x] += penEdge 160 | for x in 0 ..< result.width: 161 | var a = clamp(abs(alphas[x]) / float32(quality), 0.0, 1.0) 162 | var color = ColorRgba(r: 255, g: 255, b: 255, a: uint8(a * 255.0)) 163 | result[x, h-y-1] = color 164 | 165 | # proc getGlyphOutlineImage*( 166 | # font: Font, 167 | # unicode: string, 168 | # lines=true, 169 | # points=true, 170 | # winding=true 171 | # ): Image = 172 | # ## Get an outline of the glyph with controls points. Useful for debugging. 173 | # var glyph = font.typeface.glyphs[unicode] 174 | 175 | # const 176 | # green = ColorRgba(r: 0, g: 255, b: 0, a: 255) 177 | # red = ColorRgba(r: 255, g: 0, b: 0, a: 255) 178 | # blue = ColorRgba(r: 0, g: 0, b: 255, a: 255) 179 | 180 | # glyph.makeReady(font) 181 | 182 | # var fontHeight = font.typeface.ascent - font.typeface.descent 183 | # var scale = font.size / (fontHeight) 184 | # var tx = int floor(glyph.bboxMin.x * scale) 185 | # var ty = int floor(glyph.bboxMin.y * scale) 186 | # var w = int(ceil(glyph.bboxMax.x * scale)) - tx + 1 187 | # var h = int(ceil(glyph.bboxMax.y * scale)) - ty + 1 188 | 189 | # result = newImage(w, h) 190 | # let origin = vec2(float32 tx, float32 ty) 191 | 192 | # proc adjust(v: Vec2): Vec2 = (v) * scale - origin 193 | 194 | # proc flip(v: Vec2): Vec2 = 195 | # result.x = v.x 196 | # result.y = float32(h) - v.y 197 | 198 | # let ctx = newContext(result) 199 | 200 | # # Draw the outline. 201 | # for segments in glyph.segments: 202 | # if lines: 203 | # # Draw lines. 204 | # ctx.strokeStyle = red 205 | # for s in segments: 206 | # ctx.strokeSegment(segment(flip(adjust(s.at)), flip(adjust(s.to)))) 207 | # if points: 208 | # # Draw points. 209 | # ctx.strokeStyle = blue 210 | # for ruleNum, c in cast[PathShim](glyph.path).commands: 211 | 212 | # for i in 0.. s.to.y 236 | # var color = if winding: blue else: green 237 | 238 | # if length > 0: 239 | # # Triangle. 240 | # ctx.strokeStyle = color 241 | # let 242 | # head = mid + dir 243 | # left = mid - dir + dir2 244 | # right = mid - dir - dir2 245 | # ctx.strokeSegment(segment(head, left)) 246 | # ctx.strokeSegment(segment(left, right)) 247 | # ctx.strokeSegment(segment(right, head)) 248 | 249 | proc getGlyphImage*( 250 | font: Font, 251 | unicode: string, 252 | glyphOffset: var Vec2, 253 | subPixelShift = 0.0 254 | ): Image = 255 | ## Get an image of the glyph and the glyph offset the image should be drawn 256 | var glyph = font.typeface.glyphs[unicode] 257 | font.getGlyphImage(glyph, glyphOffset) 258 | 259 | proc getGlyphImage*(font: Font, unicode: string): Image = 260 | ## Get an image of the glyph 261 | var glyphOffset: Vec2 262 | font.getGlyphImage(unicode, glyphOffset) 263 | 264 | proc drawGlyph*(font: Font, image: Image, at: Vec2, c: string) = 265 | ## Draw glyph at a location on the image 266 | var at = at 267 | at.y += font.lineHeight 268 | if c in font.typeface.glyphs: 269 | var glyph = font.typeface.glyphs[c] 270 | if glyph.shapes.len > 0: 271 | var origin = vec2(0, 0) 272 | var img = font.getGlyphImage(glyph, origin) 273 | image.draw(img, translate(origin + at)) 274 | 275 | proc getGlyphImageOffset*( 276 | font: Font, 277 | glyph: Glyph, 278 | quality = 4, 279 | subPixelShift: float32 = 0.0, 280 | ): Vec2 = 281 | ## Get image for this glyph 282 | glyph.makeReady(font) 283 | var fontHeight = font.typeface.ascent - font.typeface.descent 284 | var scale = font.size / fontHeight 285 | var tx = int floor(glyph.bboxMin.x * scale) 286 | var ty = int floor(glyph.bboxMin.y * scale) 287 | #var w = int(ceil(glyph.bboxMax.x * scale)) - tx + 1 288 | var h = int(ceil(glyph.bboxMax.y * scale)) - ty + 1 289 | 290 | let origin = vec2(float32 tx, float32 ty) 291 | 292 | result.x = origin.x 293 | result.y = -float32(h) - origin.y 294 | 295 | proc alphaToBlackAndWhite*(image: Image) = 296 | ## Typography deals mostly with transparent images with white text 297 | ## This is hard to see in tests so we convert it to white background 298 | ## with black text. 299 | for c in image.data.mitems: 300 | c.r = 255 - c.a 301 | c.g = 255 - c.a 302 | c.b = 255 - c.a 303 | c.a = 255 304 | -------------------------------------------------------------------------------- /src/typography/svgfont.nim: -------------------------------------------------------------------------------- 1 | import font, os, streams, strutils, tables, vmath, xmlparser, xmltree 2 | 3 | proc readFontSvg*(f: Stream): Font = 4 | ## Read an SVG font from a stream. 5 | var font = Font() 6 | font.typeface = Typeface() 7 | font.size = 16 8 | font.lineHeight = 20 9 | font.typeface.glyphs = initTable[string, Glyph]() 10 | 11 | var xml = parseXml(f) 12 | 13 | for tag in xml.findAll "font": 14 | var name = tag.attr "id" 15 | if name.len > 0: 16 | font.typeface.name = name 17 | var advance = tag.attr "horiz-adv-x" 18 | if advance.len > 0: 19 | font.typeface.advance = parseFloat(advance) 20 | 21 | for tag in xml.findAll "font-face": 22 | var unitsPerEm = tag.attr "units-per-em" 23 | if unitsPerEm.len > 0: 24 | font.typeface.unitsPerEm = parseFloat(unitsPerEm) 25 | var bbox = tag.attr "bbox" 26 | if bbox.len > 0: 27 | var v = bbox.split() 28 | font.typeface.bboxMin = vec2(parseFloat(v[0]), parseFloat(v[1])) 29 | font.typeface.bboxMax = vec2(parseFloat(v[2]), parseFloat(v[3])) 30 | var capHeight = tag.attr "cap-height" 31 | if capHeight.len > 0: 32 | font.typeface.capHeight = parseFloat(capHeight) 33 | var xHeight = tag.attr "x-height" 34 | if xHeight.len > 0: 35 | font.typeface.xHeight = parseFloat(xHeight) 36 | var ascent = tag.attr "ascent" 37 | if ascent.len > 0: 38 | font.typeface.ascent = parseFloat(ascent) 39 | var descent = tag.attr "descent" 40 | if descent.len > 0: 41 | font.typeface.descent = parseFloat(descent) 42 | 43 | for tag in xml.findAll "glyph": 44 | var glyph = Glyph() 45 | glyph.code = tag.attr "unicode" 46 | let name = tag.attr "glyph-name" 47 | var advance = tag.attr "horiz-adv-x" 48 | if advance.len > 0: 49 | glyph.advance = parseFloat(advance) 50 | else: 51 | glyph.advance = font.typeface.advance 52 | glyph.pathStr = tag.attr "d" 53 | if name == "space" and glyph.code == "": 54 | glyph.code = " " 55 | font.typeface.glyphs[glyph.code] = glyph 56 | 57 | font.typeface.kerning = initTable[(string, string), float32]() 58 | for tag in xml.findAll "hkern": 59 | var k = parseFloat tag.attr "k" 60 | var u1 = tag.attr "u1" 61 | var u2 = tag.attr "u2" 62 | if u1.len > 0 and u2.len > 0: 63 | font.typeface.kerning[(u1, u2)] = k 64 | 65 | return font 66 | 67 | proc readFontSvg*(filename: string): Font = 68 | ## Read an SVG font from a file. 69 | if not fileExists(filename): 70 | raise newException(OSError, "File " & filename & " not found") 71 | var f = newFileStream(filename) 72 | result = readFontSvg(f) 73 | result.typeface.filename = filename 74 | -------------------------------------------------------------------------------- /src/typography/systemfonts.nim: -------------------------------------------------------------------------------- 1 | ## Utils for finding installed and system fonts. 2 | 3 | import algorithm, os, strutils, font 4 | 5 | const fontDirectories* = 6 | when defined(MacOSX): 7 | [ 8 | "/System/Library/Fonts/", 9 | "/Library/Fonts/", 10 | getHomeDir() & "/Library/Fonts/" 11 | ] 12 | elif defined(windows): 13 | [ 14 | r"C:\Windows\Fonts", 15 | ] 16 | else: 17 | # TODO: linux paths 18 | [] 19 | 20 | proc getSystemFonts*(): seq[string] = 21 | ## Get a list of all of the installed fonts. 22 | for fontDir in fontDirectories: 23 | for kind, path in walkDir(fontDir): 24 | if kind == pcFile and path.splitFile().ext in [".ttf", ".otf"]: 25 | result.add(path) 26 | sort(result) 27 | 28 | proc findFont*(fontName: string): string = 29 | ## Find a font given a font name. 30 | for fontDir in fontDirectories: 31 | for kind, path in walkDir(fontDir): 32 | if path.splitFile().name.toLowerAscii() == fontName.toLowerAscii(): 33 | return path 34 | raise newException(TypographyError, "Font " & fontName & " not found") 35 | 36 | proc getSystemFontPath*(): string = 37 | ## Gets the path to the system font. 38 | ## * San Francisco on MacOS. 39 | ## * Arial on Windows. 40 | ## * Linux - not implemented. 41 | when defined(macOS): 42 | "/System/Library/Fonts/SFNSText.ttf" 43 | elif defined(windows): 44 | r"C:\Windows\Fonts\arial.ttf" 45 | # else defined(linux): 46 | # TODO: go through these direcotires 47 | # "~/.fonts/" 48 | # "/usr/share/fonts/truetype/" 49 | # "/usr/X11R6/lib/X11/fonts/ttfonts/" 50 | # "/usr/X11R6/lib/X11/fonts/" 51 | # "/usr/share/fonts/truetype/" 52 | else: 53 | "" 54 | 55 | when isMainModule: 56 | echo getSystemFonts() 57 | echo findFont("Arial") 58 | echo getSystemFontPath() 59 | -------------------------------------------------------------------------------- /tests/benchmark_otf.nim: -------------------------------------------------------------------------------- 1 | import typography, benchy, common, strutils 2 | 3 | let fontPaths = findAllFonts("tests/fonts") 4 | 5 | for path in fontPaths: 6 | if path.endsWith("SourceSansPro.otf"): 7 | continue # Doesn't work for some reason 8 | timeIt path: 9 | let font = readFontOtf(path) 10 | for glyph in font.typeface.glyphArr: 11 | parseGlyph(glyph, font) 12 | -------------------------------------------------------------------------------- /tests/common.nim: -------------------------------------------------------------------------------- 1 | import os, algorithm, pixie, chroma 2 | 3 | proc findAllFonts*(rootPath: string): seq[string] = 4 | for fontPath in walkDirRec(rootPath): 5 | if splitFile(fontPath).ext in [".ttf", ".otf"]: 6 | result.add(fontPath) 7 | result.sort() 8 | 9 | proc strokeRectInner*(image: Image, rect: Rect, rgba: ColorRGBA) = 10 | ## Draws a rectangle borders only. 11 | let 12 | at = rect.xy.floor + vec2(0.5, 0.5) 13 | wh = rect.wh.floor - vec2(1, 1) # line width 14 | ctx = newContext(image) 15 | ctx.strokeStyle = rgba 16 | ctx.strokeRect(rect(at, wh)) 17 | -------------------------------------------------------------------------------- /tests/config.nims: -------------------------------------------------------------------------------- 1 | --path:"../src" 2 | -------------------------------------------------------------------------------- /tests/fontglyphs.nim: -------------------------------------------------------------------------------- 1 | ## Loads ttf system fonts and prodces a grid of their glphys 2 | 3 | import algorithm, chroma, pixie, math, os, ospaths, print, sequtils, strutils, 4 | tables, typography, typography/sysfonts, vmath 5 | 6 | proc alphaWhite(image: var Image) = 7 | ## Typography deals mostly with transperant images with white text 8 | ## This is hard to see in tests so we convert it to white background 9 | ## with black text. 10 | for x in 0..= 1000: 78 | x = 0 79 | y += 100 80 | ctx.writeFile("samples/" & name & ".png") 81 | -------------------------------------------------------------------------------- /tests/fonts/Changa-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/fonts/Changa-Bold.ttf -------------------------------------------------------------------------------- /tests/fonts/DejaVuSans.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/fonts/DejaVuSans.ttf -------------------------------------------------------------------------------- /tests/fonts/IBMPlexSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/fonts/IBMPlexSans-Regular.ttf -------------------------------------------------------------------------------- /tests/fonts/Jura-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/fonts/Jura-Regular.ttf -------------------------------------------------------------------------------- /tests/fonts/Lato-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/fonts/Lato-Regular.ttf -------------------------------------------------------------------------------- /tests/fonts/Moon Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/fonts/Moon Bold.otf -------------------------------------------------------------------------------- /tests/fonts/SourceSansPro-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/fonts/SourceSansPro-Regular.ttf -------------------------------------------------------------------------------- /tests/fonts/SourceSansPro.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/fonts/SourceSansPro.otf -------------------------------------------------------------------------------- /tests/fonts/Ubuntu.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/fonts/Ubuntu.ttf -------------------------------------------------------------------------------- /tests/fonts/Vera.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/fonts/Vera.ttf -------------------------------------------------------------------------------- /tests/fonts/hanazono/HanaMinA.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/fonts/hanazono/HanaMinA.ttf -------------------------------------------------------------------------------- /tests/fonts/hanazono/HanaMinB.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/fonts/hanazono/HanaMinB.ttf -------------------------------------------------------------------------------- /tests/fonts/hanazono/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The Hanazono font is dual-licensed under the Hanazono Font 2 | License and the Open Font License. 3 | 4 | =========================================================== 5 | (Hanazono Font License) 6 | 7 | ----------------------------------------------------------- 8 | Hanazono Font License 9 | ----------------------------------------------------------- 10 | 11 | This font is a free software. 12 | Unlimited permission is granted to use, copy, and distribute it, with 13 | or without modification, either commercially and noncommercially. 14 | THIS FONT IS PROVIDED "AS IS" WITHOUT WARRANTY. 15 | License of this document is same as the font. 16 | 17 | Copyright 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2016, 2017 GlyphWiki Project. 18 | 19 | =========================================================== 20 | (Open Font License) 21 | 22 | Copyright (c) 2013, GlyphWiki Project (kamichi@ic.daito.ac.jp), 23 | with Reserved Font Name "Hanazono Font", "HanaMinA", "HanaMinB", 24 | "花園フォント", "花園明朝A", and "花園明朝B". 25 | 26 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 27 | This license is copied below, and is also available with a FAQ at: 28 | http://scripts.sil.org/OFL 29 | 30 | ----------------------------------------------------------- 31 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 32 | ----------------------------------------------------------- 33 | 34 | PREAMBLE 35 | The goals of the Open Font License (OFL) are to stimulate worldwide 36 | development of collaborative font projects, to support the font creation 37 | efforts of academic and linguistic communities, and to provide a free and 38 | open framework in which fonts may be shared and improved in partnership 39 | with others. 40 | 41 | The OFL allows the licensed fonts to be used, studied, modified and 42 | redistributed freely as long as they are not sold by themselves. The 43 | fonts, including any derivative works, can be bundled, embedded, 44 | redistributed and/or sold with any software provided that any reserved 45 | names are not used by derivative works. The fonts and derivatives, 46 | however, cannot be released under any other type of license. The 47 | requirement for fonts to remain under this license does not apply 48 | to any document created using the fonts or their derivatives. 49 | 50 | DEFINITIONS 51 | "Font Software" refers to the set of files released by the Copyright 52 | Holder(s) under this license and clearly marked as such. This may 53 | include source files, build scripts and documentation. 54 | 55 | "Reserved Font Name" refers to any names specified as such after the 56 | copyright statement(s). 57 | 58 | "Original Version" refers to the collection of Font Software components as 59 | distributed by the Copyright Holder(s). 60 | 61 | "Modified Version" refers to any derivative made by adding to, deleting, 62 | or substituting -- in part or in whole -- any of the components of the 63 | Original Version, by changing formats or by porting the Font Software to a 64 | new environment. 65 | 66 | "Author" refers to any designer, engineer, programmer, technical 67 | writer or other person who contributed to the Font Software. 68 | 69 | PERMISSION & CONDITIONS 70 | Permission is hereby granted, free of charge, to any person obtaining 71 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 72 | redistribute, and sell modified and unmodified copies of the Font 73 | Software, subject to the following conditions: 74 | 75 | 1) Neither the Font Software nor any of its individual components, 76 | in Original or Modified Versions, may be sold by itself. 77 | 78 | 2) Original or Modified Versions of the Font Software may be bundled, 79 | redistributed and/or sold with any software, provided that each copy 80 | contains the above copyright notice and this license. These can be 81 | included either as stand-alone text files, human-readable headers or 82 | in the appropriate machine-readable metadata fields within text or 83 | binary files as long as those fields can be easily viewed by the user. 84 | 85 | 3) No Modified Version of the Font Software may use the Reserved Font 86 | Name(s) unless explicit written permission is granted by the corresponding 87 | Copyright Holder. This restriction only applies to the primary font name as 88 | presented to the users. 89 | 90 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 91 | Software shall not be used to promote, endorse or advertise any 92 | Modified Version, except to acknowledge the contribution(s) of the 93 | Copyright Holder(s) and the Author(s) or with their explicit written 94 | permission. 95 | 96 | 5) The Font Software, modified or unmodified, in part or in whole, 97 | must be distributed entirely under this license, and must not be 98 | distributed under any other license. The requirement for fonts to 99 | remain under this license does not apply to any document created 100 | using the Font Software. 101 | 102 | TERMINATION 103 | This license becomes null and void if any of the above conditions are 104 | not met. 105 | 106 | DISCLAIMER 107 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 108 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 109 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 110 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 111 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 112 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 113 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 114 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 115 | OTHER DEALINGS IN THE FONT SOFTWARE. 116 | 117 | =========================================================== 118 | 119 | 花園フォントは、独自ライセンスとSIL OFLライセンスのデュアルライセンス 120 | です。SIL OFLライセンスは上記英文を参照してください。 121 | 122 | ----------------------------------------------------------- 123 | 花園フォント ライセンス 124 | ----------------------------------------------------------- 125 | 126 | このフォントはフリー(自由な)ソフトウエアです。 127 | あらゆる改変の有無に関わらず、また商業的な利用であっても、自由に利用、 128 | 複製、再配布することができますが、全て無保証とさせていただきます。 129 | このライセンス文書についても、フォントと同様の扱いとします。 130 | 131 | Copyright 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2016, 2017 GlyphWiki Project. 132 | -------------------------------------------------------------------------------- /tests/fonts/hanazono/THANKS.txt: -------------------------------------------------------------------------------- 1 | Thanks 2 | 3 | A part of this font was completed with the support of Grant-in-Aid for 4 | Publication of Scientific Research Results from Japan Society for the 5 | Promotion of Science and the International Research Institute for 6 | Zen Buddhism (IRIZ), Hanazono University. 7 | 8 | ---------- 9 | 10 | 謝辞 11 | 12 | このフォントのJIS X 0208:1997 収録文字集合の一部は、2005年度 日本学術 13 | 振興会科学研究費補助金(研究公開促進費)および花園大学国際禅学研究所[1] 14 | 文部科学省・学術フロンティア推進事業 の補助により漢字データベース計画[2] 15 | で作成したデータを利用しています。 16 | 17 | このフォントの収録文字集合の一部は、科学研究費補助金(課題番号:22700262) 18 | (課題番号:23320010)(課題番号:23320077)による研究成果の一部です。 19 | 20 | このフォントは、グリフウィキ[3]に登録ユーザーとして、あるいは匿名ユーザー 21 | として漢字字形データを登録・改良してくださった多数の方々によるボランティ 22 | アの活動から成り立っています。グリフウィキは、科学研究費補助金(前述の 23 | ほか課題番号:16H03351)による研究成果の一部です。 24 | 25 | 縦書き表示用のパラメータ生成には漢字データベース計画が公開している情報[4] 26 | を参考としています。 27 | 28 | [1] http://iriz.hanazono.ac.jp/ 29 | [2] http://kanji-database.sourceforge.net/ 30 | [3] http://glyphwiki.org/ 31 | [4] http://kanji-database.sourceforge.net/fonts/opentype.html 32 | 33 | -------------------------------------------------------------------------------- /tests/fonts/silver.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/fonts/silver.ttf -------------------------------------------------------------------------------- /tests/fuzz_otf.nim: -------------------------------------------------------------------------------- 1 | import os, strutils, random, strformat, common 2 | 3 | include typography/opentype/parser 4 | 5 | randomize() 6 | 7 | let fontPaths = findAllFonts("tests/fonts") 8 | 9 | for i in 0 ..< 10000: 10 | var 11 | file = fontPaths[rand(fontPaths.len - 1)] 12 | data = readFile(file) 13 | pos = rand(data.len) 14 | value = rand(255).char 15 | data[pos] = value 16 | echo &"{i} {file} {pos} {value.uint8}" 17 | try: 18 | let font = parseOtf(data) 19 | doAssert font != nil 20 | for glyph in font.typeface.glyphArr: 21 | parseGlyph(glyph, font) 22 | except TypographyError: 23 | discard 24 | 25 | data = data[0 ..< pos] 26 | try: 27 | let font = parseOtf(data) 28 | doAssert font != nil 29 | for glyph in font.typeface.glyphArr: 30 | parseGlyph(glyph, font) 31 | except TypographyError: 32 | discard 33 | -------------------------------------------------------------------------------- /tests/googlefonts/index.html: -------------------------------------------------------------------------------- 1 |

0

0.974% diffpx


1

0.715% diffpx


2

0.836% diffpx


3

0.704% diffpx


4

0.567% diffpx


5

0.707% diffpx


6

0.770% diffpx


7

0.635% diffpx


8

0.875% diffpx


9

0.978% diffpx


10

0.990% diffpx


11

1.139% diffpx


12

0.560% diffpx


13

0.714% diffpx


14

0.484% diffpx


15

0.691% diffpx


16

0.664% diffpx


17

0.866% diffpx


18

0.611% diffpx


19

0.842% diffpx


20

0.692% diffpx


21

0.708% diffpx


22

0.720% diffpx


23

0.718% diffpx


24

1.123% diffpx


25

0.712% diffpx


26

0.724% diffpx


27

0.698% diffpx


28

0.733% diffpx


29

0.723% diffpx


30

0.871% diffpx


31

0.680% diffpx


32

0.815% diffpx


33

0.907% diffpx


34

0.483% diffpx


35

0.684% diffpx


36

0.683% diffpx


37

0.830% diffpx


38

0.644% diffpx


39

0.726% diffpx


40

0.607% diffpx


-------------------------------------------------------------------------------- /tests/googlefonts/masters/0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/googlefonts/masters/0.png -------------------------------------------------------------------------------- /tests/googlefonts/masters/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/googlefonts/masters/1.png -------------------------------------------------------------------------------- /tests/googlefonts/masters/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/googlefonts/masters/10.png -------------------------------------------------------------------------------- /tests/googlefonts/masters/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/googlefonts/masters/11.png -------------------------------------------------------------------------------- /tests/googlefonts/masters/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/googlefonts/masters/12.png -------------------------------------------------------------------------------- /tests/googlefonts/masters/13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/googlefonts/masters/13.png -------------------------------------------------------------------------------- /tests/googlefonts/masters/14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/googlefonts/masters/14.png -------------------------------------------------------------------------------- /tests/googlefonts/masters/15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/googlefonts/masters/15.png -------------------------------------------------------------------------------- /tests/googlefonts/masters/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/googlefonts/masters/16.png -------------------------------------------------------------------------------- /tests/googlefonts/masters/17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/googlefonts/masters/17.png -------------------------------------------------------------------------------- /tests/googlefonts/masters/18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/googlefonts/masters/18.png -------------------------------------------------------------------------------- /tests/googlefonts/masters/19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/googlefonts/masters/19.png -------------------------------------------------------------------------------- /tests/googlefonts/masters/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/googlefonts/masters/2.png -------------------------------------------------------------------------------- /tests/googlefonts/masters/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/googlefonts/masters/20.png -------------------------------------------------------------------------------- /tests/googlefonts/masters/21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/googlefonts/masters/21.png -------------------------------------------------------------------------------- /tests/googlefonts/masters/22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/googlefonts/masters/22.png -------------------------------------------------------------------------------- /tests/googlefonts/masters/23.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/googlefonts/masters/23.png -------------------------------------------------------------------------------- /tests/googlefonts/masters/24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/googlefonts/masters/24.png -------------------------------------------------------------------------------- /tests/googlefonts/masters/25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/googlefonts/masters/25.png -------------------------------------------------------------------------------- /tests/googlefonts/masters/26.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/googlefonts/masters/26.png -------------------------------------------------------------------------------- /tests/googlefonts/masters/27.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/googlefonts/masters/27.png -------------------------------------------------------------------------------- /tests/googlefonts/masters/28.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/googlefonts/masters/28.png -------------------------------------------------------------------------------- /tests/googlefonts/masters/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/googlefonts/masters/29.png -------------------------------------------------------------------------------- /tests/googlefonts/masters/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/googlefonts/masters/3.png -------------------------------------------------------------------------------- /tests/googlefonts/masters/30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/googlefonts/masters/30.png -------------------------------------------------------------------------------- /tests/googlefonts/masters/31.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/googlefonts/masters/31.png -------------------------------------------------------------------------------- /tests/googlefonts/masters/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/googlefonts/masters/32.png -------------------------------------------------------------------------------- /tests/googlefonts/masters/33.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/googlefonts/masters/33.png -------------------------------------------------------------------------------- /tests/googlefonts/masters/34.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/googlefonts/masters/34.png -------------------------------------------------------------------------------- /tests/googlefonts/masters/35.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/googlefonts/masters/35.png -------------------------------------------------------------------------------- /tests/googlefonts/masters/36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/googlefonts/masters/36.png -------------------------------------------------------------------------------- /tests/googlefonts/masters/37.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/googlefonts/masters/37.png -------------------------------------------------------------------------------- /tests/googlefonts/masters/38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/googlefonts/masters/38.png -------------------------------------------------------------------------------- /tests/googlefonts/masters/39.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/googlefonts/masters/39.png -------------------------------------------------------------------------------- /tests/googlefonts/masters/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/googlefonts/masters/4.png -------------------------------------------------------------------------------- /tests/googlefonts/masters/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/googlefonts/masters/40.png -------------------------------------------------------------------------------- /tests/googlefonts/masters/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/googlefonts/masters/5.png -------------------------------------------------------------------------------- /tests/googlefonts/masters/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/googlefonts/masters/6.png -------------------------------------------------------------------------------- /tests/googlefonts/masters/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/googlefonts/masters/7.png -------------------------------------------------------------------------------- /tests/googlefonts/masters/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/googlefonts/masters/8.png -------------------------------------------------------------------------------- /tests/googlefonts/masters/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/googlefonts/masters/9.png -------------------------------------------------------------------------------- /tests/inspect_tableusage.nim: -------------------------------------------------------------------------------- 1 | import common, cligen, typography, tables 2 | 3 | var usage: CountTable[string] 4 | 5 | proc main(fonts = "/p/googlefonts") = 6 | let fontPaths = findAllFonts(fonts) 7 | for fontPath in fontPaths: 8 | let font = readFontOtf(fontPath) 9 | for tag in font.typeface.otf.chunks.keys: 10 | usage.inc(tag) 11 | 12 | usage.sort() 13 | 14 | for tag, count in usage: 15 | let countStr = $count 16 | var dots = "" 17 | for i in tag.len + countStr.len .. 40: 18 | dots.add(".") 19 | echo tag, " ", dots, " ", countStr 20 | 21 | dispatch(main) 22 | -------------------------------------------------------------------------------- /tests/megatest.nim: -------------------------------------------------------------------------------- 1 | ## Loads google fonts and draws a text sample. 2 | 3 | import pixie, math, os, strutils, cligen, common, tables, typography, vmath, 4 | strformat, chroma, typography/systemfonts 5 | 6 | # Clone https://github.com/google/fonts 7 | # Check out commit ebaa6a7aab9b700da4e30a4682687acdf427eae7 8 | 9 | proc testString(font: Font): string = 10 | result = "The quick brown fox jumps over the lazy dog." 11 | if "q" notin font.typeface.glyphs: 12 | # can't display english, use random glpyhs: 13 | result = "" 14 | var i = 0 15 | for g in font.typeface.glyphs.values: 16 | result.add(g.code) 17 | inc i 18 | if i > 80: 19 | break 20 | 21 | proc main(fonts = "") = 22 | let (testDir, fontPaths) = 23 | if fonts.len == 0: 24 | var systemFonts = getSystemFonts() 25 | systemFonts.delete(systemFonts.find(r"C:\Windows\Fonts\DINPro.otf")) # Doesn't work yet 26 | ("systemfonts", systemFonts) 27 | else: 28 | ("googlefonts", findAllFonts(fonts)) 29 | 30 | var 31 | mainFont = readFontTtf("tests/fonts/Ubuntu.ttf") 32 | html = "" 33 | 34 | for pageNum in 0 ..< fontPaths.len div 100 + 1: 35 | echo "page ", pageNum 36 | var image = newImage(800, 100*40) 37 | for fontNum in 0 .. 100: 38 | if fontNum + pageNum * 100 >= fontPaths.len: 39 | break 40 | let fontPath = fontPaths[fontNum + pageNum * 100] 41 | echo fontPath 42 | 43 | var font = readFontTtf(fontPath) 44 | font.size = 20 45 | font.lineHeight = 20 46 | 47 | let y = fontNum.float32 * 40 + 10 48 | 49 | mainFont.size = 10 50 | mainFont.lineHeight = 20 51 | mainFont.drawText(image, vec2(10, y), fontPath.lastPathPart) 52 | 53 | let 54 | topLine = y + 20 + (-font.typeface.ascent + font.typeface.descent) * font.scale 55 | capLine = y + font.capline 56 | baseLine = y + font.baseline 57 | bottomLine = y + 20 58 | 59 | for line in [topLine, capLine, baseLine, bottomLine]: 60 | var path: Path 61 | path.rect(300, line, 500, 1) 62 | image.fillPath(path, rgba(0, 0, 0, 255)) 63 | 64 | font.drawText( 65 | image, 66 | vec2(300, y), 67 | font.testString() 68 | ) 69 | 70 | image.alphaToBlackAndWhite() 71 | echo &"saving {testDir} page {pageNum}" 72 | createDir(&"tests/{testDir}/out") 73 | image.writeFile(&"tests/{testDir}/out/{pageNum}.png") 74 | 75 | let master = 76 | if fileExists(&"tests/{testDir}/masters/{pageNum}.png"): 77 | readImage(&"tests/{testDir}/masters/{pageNum}.png") 78 | else: 79 | newImage(1, 1) 80 | 81 | let (score, diff) = diff(master, image) 82 | 83 | createDir(&"tests/{testDir}/diffs") 84 | diff.writeFile(&"tests/{testDir}/diffs/{pageNum}.png") 85 | 86 | html.add(&"

{pageNum}

") 87 | html.add(&"

{score:0.3f}% diffpx

") 88 | html.add(&"") 89 | html.add(&"") 90 | html.add(&"") 91 | html.add("
") 92 | 93 | writeFile(&"tests/{testDir}/index.html", html) 94 | 95 | dispatch(main) 96 | -------------------------------------------------------------------------------- /tests/rendered/alignment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/rendered/alignment.png -------------------------------------------------------------------------------- /tests/rendered/basicSvg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/rendered/basicSvg.png -------------------------------------------------------------------------------- /tests/rendered/basicTtf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/rendered/basicTtf.png -------------------------------------------------------------------------------- /tests/rendered/basicTtf2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/rendered/basicTtf2.png -------------------------------------------------------------------------------- /tests/rendered/ch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/rendered/ch.png -------------------------------------------------------------------------------- /tests/rendered/font_metrics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/rendered/font_metrics.png -------------------------------------------------------------------------------- /tests/rendered/font_metrics_blur.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/rendered/font_metrics_blur.png -------------------------------------------------------------------------------- /tests/rendered/hFill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/rendered/hFill.png -------------------------------------------------------------------------------- /tests/rendered/layout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/rendered/layout.png -------------------------------------------------------------------------------- /tests/rendered/layoutNoText.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/rendered/layoutNoText.png -------------------------------------------------------------------------------- /tests/rendered/picking.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/rendered/picking.png -------------------------------------------------------------------------------- /tests/rendered/qFill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/rendered/qFill.png -------------------------------------------------------------------------------- /tests/rendered/qOutLine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/rendered/qOutLine.png -------------------------------------------------------------------------------- /tests/rendered/ru.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/rendered/ru.png -------------------------------------------------------------------------------- /tests/rendered/sample_blur.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/rendered/sample_blur.png -------------------------------------------------------------------------------- /tests/rendered/sample_svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/rendered/sample_svg.png -------------------------------------------------------------------------------- /tests/rendered/sample_ttf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/rendered/sample_ttf.png -------------------------------------------------------------------------------- /tests/rendered/scaledup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/rendered/scaledup.png -------------------------------------------------------------------------------- /tests/rendered/selection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/rendered/selection.png -------------------------------------------------------------------------------- /tests/rendered/sizes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/rendered/sizes.png -------------------------------------------------------------------------------- /tests/rendered/subpixelglyphs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/rendered/subpixelglyphs.png -------------------------------------------------------------------------------- /tests/rendered/subpixelpos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/rendered/subpixelpos.png -------------------------------------------------------------------------------- /tests/rendered/tabs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/rendered/tabs.png -------------------------------------------------------------------------------- /tests/rendered/test_char.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/rendered/test_char.png -------------------------------------------------------------------------------- /tests/rendered/test_charFill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/rendered/test_charFill.png -------------------------------------------------------------------------------- /tests/rendered/test_otf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/rendered/test_otf.png -------------------------------------------------------------------------------- /tests/rendered/test_svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/rendered/test_svg.png -------------------------------------------------------------------------------- /tests/rendered/test_ttf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/rendered/test_ttf.png -------------------------------------------------------------------------------- /tests/rendered/wordwrap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/rendered/wordwrap.png -------------------------------------------------------------------------------- /tests/rendered/wordwrapch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/rendered/wordwrapch.png -------------------------------------------------------------------------------- /tests/samples/sample.ch.txt: -------------------------------------------------------------------------------- 1 |  乾隆己酉夏,以編排秘籍,于役灤陽,時校理久竟,特督視官吏,題簽庋架 2 | 而已,晝長無事,追錄見聞,憶及即書,都無體例,小說稗官,知無關於著述; 3 | 街談巷議,或有益於勸懲,聊付抄胥存之。命曰《灤陽消夏錄》云爾。 4 |    5 |   胡御史牧亭言,其里有人畜一豬,見鄰叟輒瞋目狂吼,奔突欲噬,見他人則 6 | 否。鄰叟初甚怒之,欲買而啖其肉。既而憬然省曰:「此殆佛經所謂夙冤耶?世 7 | 無不可解之冤。」乃以善價贖得,送佛寺為長生豬,後再見之,弭耳昵就,非復 8 | 曩態矣。嘗見孫重畫伏虎應真,有巴西李衍題曰:「至人騎猛虎,馭之猶騏驥。 9 | 豈伊本馴良,道力消其鷙。乃知天地間,有情皆可契。共保金石心,無為多畏忌 10 | 。」可為此事作解也。 11 |    12 |   滄州劉士玉孝廉,有書室為狐所據。白晝與人對語,擲瓦石擊人,但不睹其 13 | 形耳。知州平原董思任,良吏也,聞其事,自往驅之。方盛陳人妖異路之理,忽 14 | 簷際朗言曰:「公為官,頗愛民,亦不取錢,故我不敢擊公,然公愛民乃好名, 15 | 不取錢乃畏後患耳,故我亦不避公。公休矣,毋多言取困。」董狼狽而歸,咄咄 16 | 不怡者數日。劉一僕婦甚粗蠢,獨不畏狐。狐亦不擊之,或於對語時,舉以問狐 17 | 。狐曰:「彼雖下役,乃真孝婦也,鬼神見之猶斂避,況我曹乎?」劉乃令僕婦 18 | 居此室,狐是日即去。 19 | 20 |   愛堂先生言,聞有老學究夜行,忽遇其亡友,學究素剛直,亦不怖畏,問君 21 | 何往,曰:「吾為冥吏,至南村有所勾攝,適同路耳。」因並行。至一破屋,鬼 22 | 曰:「此文士廬也。」問:「何以知之?」曰:「凡人白晝營營,性靈汨沒,唯 23 | 睡時一念不生,元神朗澈,胸中所讀之書,字字皆吐光芒,自百竅而出,其狀縹 24 | 渺繽紛,爛如錦繡。學如鄭孔,文如屈宋班馬者,上燭霄漢,與星月爭輝;次者 25 | 數丈,次者數尺,以漸而差,極下者亦螢螢如一燈,照映戶牖。人不能見,唯鬼 26 | 神見之耳。此室上光芒高七八尺,以是而知。」學究問:「我讀書一生,睡中光 27 | 芒當幾許?」鬼囁嚅良久曰:「昨過君塾,君方晝寢,見君胸中高頭講章一部, 28 | 墨卷五六百篇,經文七八十篇,策略三四十篇,字字化為黑煙,籠罩屋上,諸生 29 | 誦讀之聲,如在濃雲密霧中,實未見光芒,不敢妄語。」學究怒斥之,鬼大笑而 30 | 去。 31 | -------------------------------------------------------------------------------- /tests/samples/sample.ch.wrap.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 乾隆己酉夏,以編排秘籍,于役灤陽,時校理久竟,特督視官吏,題簽庋架 而已,晝長無事,追錄見聞,憶及即書,都無體例,小說稗官,知無關於著述; 街談巷議,或有益於勸懲,聊付抄胥存之。命曰《灤陽消夏錄》云爾。 5 | 6 | 胡御史牧亭言,其里有人畜一豬,見鄰叟輒瞋目狂吼,奔突欲噬,見他人則 否。鄰叟初甚怒之,欲買而啖其肉。既而憬然省曰:「此殆佛經所謂夙冤耶?世 無不可解之冤。」乃以善價贖得,送佛寺為長生豬,後再見之,弭耳昵就,非復 曩態矣。嘗見孫重畫伏虎應真,有巴西李衍題曰:「至人騎猛虎,馭之猶騏驥。 豈伊本馴良,道力消其鷙。乃知天地間,有情皆可契。共保金石心,無為多畏忌 。」可為此事作解也。 7 | 8 | 滄州劉士玉孝廉,有書室為狐所據。白晝與人對語,擲瓦石擊人,但不睹其 形耳。知州平原董思任,良吏也,聞其事,自往驅之。方盛陳人妖異路之理,忽 簷際朗言曰:「公為官,頗愛民,亦不取錢,故我不敢擊公,然公愛民乃好名, 不取錢乃畏後患耳,故我亦不避公。公休矣,毋多言取困。」董狼狽而歸,咄咄 不怡者數日。劉一僕婦甚粗蠢,獨不畏狐。狐亦不擊之,或於對語時,舉以問狐 。狐曰:「彼雖下役,乃真孝婦也,鬼神見之猶斂避,況我曹乎?」劉乃令僕婦 居此室,狐是日即去。 9 | 10 | 愛堂先生言,聞有老學究夜行,忽遇其亡友,學究素剛直,亦不怖畏,問君 何往,曰:「吾為冥吏,至南村有所勾攝,適同路耳。」因並行。至一破屋,鬼 曰:「此文士廬也。」問:「何以知之?」曰:「凡人白晝營營,性靈汨沒,唯 睡時一念不生,元神朗澈,胸中所讀之書,字字皆吐光芒,自百竅而出,其狀縹 渺繽紛,爛如錦繡。學如鄭孔,文如屈宋班馬者,上燭霄漢,與星月爭輝;次者 數丈,次者數尺,以漸而差,極下者亦螢螢如一燈,照映戶牖。人不能見,唯鬼 神見之耳。此室上光芒高七八尺,以是而知。」學究問:「我讀書一生,睡中光 芒當幾許?」鬼囁嚅良久曰:「昨過君塾,君方晝寢,見君胸中高頭講章一部, 墨卷五六百篇,經文七八十篇,策略三四十篇,字字化為黑煙,籠罩屋上,諸生 誦讀之聲,如在濃雲密霧中,實未見光芒,不敢妄語。」學究怒斥之,鬼大笑而 去。 -------------------------------------------------------------------------------- /tests/samples/sample.fr.txt: -------------------------------------------------------------------------------- 1 | La maisonnette avait un aspect frais et souriant qui réjouissait le coeur; la vigne vierge et le houblon tapissaient ses murs; sept ou huit fenêtres percées irrégulièrement, et toutes grandes ouvertes au midi, semblaient regarder la campagne avec bonhomie; un mince filet de fumée tremblait au bout de la cheminée, où pendaient les tiges flexibles des pariétaires, et à quelque heure du jour que l'on passât devant la maisonnette, on y entendait des cris joyeux d'enfants mêlés au chant du coq. Parmi ces enfants qui venaient là de tous les coins du faubourg, il y en avait trois qui appartenaient à Guillaume Grinedal, le maître du logis: Jacques, Claudine et Pierre. -------------------------------------------------------------------------------- /tests/samples/sample.ko.txt: -------------------------------------------------------------------------------- 1 | 폰트소개 2 | 3 | 지난 시절에 대한 추억과 향수가 담겨있는 복고 서체입니다. 투박하지만 정감 있었던 4 | 옛 시절의 정서를 느낄 수 있는 폰트입니다. 5 | 6 | 사용범위 7 | 8 | 이 폰트의 지적 재산권을 포함한 모든 권리는 “㈜타이포디자인연구소”에 있습니다. 9 | 이 폰트는 “㈜타이포디자인연구소”의 사전 승인 없이는 어떠한 경우에도 수정, 변형, 10 | 복사, 대여, 임대, 재판매, 배포, 공유, 이전할 수 없습니다. 11 | 이 폰트는 개인이 비상업적인 용도로 사용하는 것에만 한하여 무료로 제공됩니다. ( 12 | 상업적인 용도로 사용 시 개인 사용자라도 별도의 구매 절차가 필요합니다.) 13 | 단체 사용자(비영리단체, 관공서 포함), 기업 사용자 등의 경우 별도의 구매 절차가 필요합니다. 14 | -------------------------------------------------------------------------------- /tests/samples/sample.ru.txt: -------------------------------------------------------------------------------- 1 | Из Керчи приехали мы в Кафу, остановились у Броневского, 2 | человека почтенного по непорочной службе и по бедности. 3 | Теперь он под судом — и, подобно старику Вергилия, 4 | разводит сад на берегу моря, недалеко от города. Виноград 5 | и миндаль составляют его доход. Он не умный человек, но 6 | имеет большие сведения об Крыме. Стороне важной и 7 | запущенной. Отсюда морем отправились мы мимо полуденных 8 | берегов Тавриды, в Юрзуф, где находилось семейство 9 | Раевского. Ночью на корабле написал я элегию, которую 10 | тебе присылаю. -------------------------------------------------------------------------------- /tests/samples/sample.txt: -------------------------------------------------------------------------------- 1 | There was a steaming mist in all the hollows, and it had roamed in its 2 | forlornness up the hill, like an evil spirit, seeking rest and finding 3 | none. A clammy and intensely cold mist, it made its slow way through the 4 | air in ripples that visibly followed and overspread one another, as the 5 | waves of an unwholesome sea might do. It was dense enough to shut out 6 | everything from the light of the coach-lamps but these its own workings, 7 | and a few yards of road; and the reek of the labouring horses steamed 8 | into it, as if they had made it all. -------------------------------------------------------------------------------- /tests/samples/sample.wrap.txt: -------------------------------------------------------------------------------- 1 | There was a steaming mist in all the hollows, and it had roamed in its forlornness up the hill, like an evil spirit, seeking rest and finding none. A clammy and intensely cold mist, it made its slow way through the air in ripples that visibly followed and overspread one another, as the waves of an unwholesome sea mighterzzzz do. It was dense enough to shut out everything from the light of the coach-lamps but these its own workings, and a few yards of road; and the reek of the labouring horses steamed into it, as if they had made it all. -------------------------------------------------------------------------------- /tests/test.nim: -------------------------------------------------------------------------------- 1 | import bumpy, chroma, pixie, print, tables, typography, vmath, os, osproc, 2 | typography/svgfont, common 3 | 4 | setCurrentDir(getCurrentDir() / "tests") 5 | 6 | proc magnifyNearest*(image: Image, scale: int): Image = 7 | ## Scales image image up by an integer scale. 8 | result = newImage( 9 | image.width * scale, 10 | image.height * scale, 11 | ) 12 | for y in 0 ..< result.height: 13 | for x in 0 ..< result.width: 14 | result.unsafe[x, y] = image.unsafe[x div scale, y div scale] 15 | 16 | proc outlineBorder*(image: Image, borderPx: int): Image = 17 | ## Adds n pixel border around alpha parts of the image. 18 | result = newImage( 19 | image.width + borderPx * 2, 20 | image.height + borderPx * 3 21 | ) 22 | for y in 0 ..< result.height: 23 | for x in 0 ..< result.width: 24 | var filled = false 25 | for bx in -borderPx .. borderPx: 26 | for by in -borderPx .. borderPx: 27 | var rgba = image[x + bx - borderPx, y - by - borderPx] 28 | if rgba.a > 0.uint8: 29 | filled = true 30 | break 31 | if filled: 32 | break 33 | if filled: 34 | result.unsafe[x, y] = rgba(255, 255, 255, 255) 35 | 36 | block: 37 | var font = readFontSvg("fonts/Ubuntu.svg") 38 | font.size = 100 39 | var image = font.getGlyphImage("h") 40 | image.alphaToBlackAndWhite() 41 | image.writeFile("rendered/hFill.png") 42 | 43 | block: 44 | var image = newImage(500, 40) 45 | var font = readFontSvg("fonts/Ubuntu.svg") 46 | font.size = 16 47 | font.lineHeight = 16 48 | font.drawText(image, vec2(10, 10), "The \"quick\" brown fox jumps over the lazy dog.") 49 | 50 | image.alphaToBlackAndWhite() 51 | image.writeFile("rendered/basicSvg.png") 52 | 53 | block: 54 | var font = readFontTtf("fonts/Ubuntu.ttf") 55 | font.size = 16 56 | var image = newImage(500, 40) 57 | 58 | font.drawText(image, vec2(10, 10), "The \"quick\" brown fox jumps over the lazy dog.") 59 | 60 | image.alphaToBlackAndWhite() 61 | image.writeFile("rendered/basicTtf.png") 62 | 63 | block: 64 | var font = readFontTtf("fonts/IBMPlexSans-Regular.ttf") 65 | font.size = 16 66 | var image = newImage(500, 40) 67 | 68 | font.drawText(image, vec2(10, 10), "The \"quick\" brown fox jumps over the lazy dog.") 69 | 70 | image.alphaToBlackAndWhite() 71 | image.writeFile("rendered/basicTtf2.png") 72 | 73 | 74 | block: 75 | var font = readFontSvg("fonts/Ubuntu.svg") 76 | var image = newImage(500, 240) 77 | 78 | font.size = 8 79 | font.drawText(image, vec2(10, 10), "The quick brown fox jumps over the lazy dog.") 80 | font.size = 10 81 | font.drawText(image, vec2(10, 25), "The quick brown fox jumps over the lazy dog.") 82 | font.size = 14 83 | font.drawText(image, vec2(10, 45), "The quick brown fox jumps over the lazy dog.") 84 | font.size = 22 85 | font.drawText(image, vec2(10, 75), "The quick brown fox jumps over the lazy dog.") 86 | font.size = 36 87 | font.drawText(image, vec2(10, 110), "The quick brown fox jumps over the lazy dog.") 88 | font.size = 72 89 | font.drawText(image, vec2(10, 180), "The quick brown fox jumps over the lazy dog.") 90 | 91 | image.alphaToBlackAndWhite() 92 | image.writeFile("rendered/sizes.png") 93 | 94 | block: 95 | var image = newImage(800, 220) 96 | var font = readFontSvg("fonts/DejaVuSans.svg") 97 | 98 | font.size = 16 99 | font.lineHeight = 20 100 | font.drawText(image, vec2(10, 10), readFile("samples/sample.ru.txt")) 101 | 102 | image.alphaToBlackAndWhite() 103 | image.writeFile("rendered/ru.png") 104 | 105 | block: 106 | var image = newImage(800, 200) 107 | var font = readFontSvg("fonts/Ubuntu.svg") 108 | font.size = 16 109 | font.lineHeight = 20 110 | print "svg:", font.typeface.ascent, font.typeface.descent, font.typeface.unitsPerEm 111 | font.drawText(image, vec2(10, 10), readFile("samples/sample.txt")) 112 | image.alphaToBlackAndWhite() 113 | image.writeFile("rendered/sample_svg.png") 114 | 115 | block: 116 | var image = newImage(800, 200) 117 | var font = readFontTtf("fonts/Ubuntu.ttf") 118 | font.size = 16 119 | font.lineHeight = 20 120 | print "ttf:", font.typeface.ascent, font.typeface.descent, font.typeface.unitsPerEm 121 | font.drawText(image, vec2(10, 10), readFile("samples/sample.txt")) 122 | image.alphaToBlackAndWhite() 123 | image.writeFile("rendered/sample_ttf.png") 124 | 125 | # block: 126 | # var image = newImage(800, 200) 127 | # var font = readFontOtf("fonts/Ubuntu.ttf") 128 | 129 | # font.size = 16 130 | # font.lineHeight = 20 131 | # font.drawText(image, vec2(10, 10), readFile("samples/sample.txt")) 132 | 133 | # image.alphaToBlackAndWhite() 134 | # image.writeFile("otf.png") 135 | 136 | block: 137 | var image = newImage(600, 620) 138 | var font = readFontTtf("fonts/hanazono/HanaMinA.ttf") 139 | font.size = 16 140 | font.lineHeight = 20 141 | 142 | font.drawText(image, vec2(10, 10), readFile("samples/sample.ch.txt")) 143 | 144 | image.alphaToBlackAndWhite() 145 | image.writeFile("rendered/ch.png") 146 | 147 | block: 148 | var image = newImage(250, 20) 149 | 150 | var font = readFontSvg("fonts/DejaVuSans.svg") 151 | font.size = 11 # 11px or 8pt 152 | font.drawText(image, vec2(10, 4), "The quick brown fox jumps over the lazy dog.") 153 | 154 | image = image.magnifyNearest(4) 155 | image.alphaToBlackAndWhite() 156 | image.writeFile("rendered/scaledup.png") 157 | 158 | block: 159 | var image = newImage(140, 20) 160 | 161 | var font = readFontSvg("fonts/DejaVuSans.svg") 162 | font.size = 11 # 11px or 8pt 163 | font.drawText(image, vec2(8, 4), "momomomomomomo") 164 | 165 | image = image.magnifyNearest(6) 166 | image.alphaToBlackAndWhite() 167 | image.writeFile("rendered/subpixelpos.png") 168 | 169 | block: 170 | var image = newImage(140, 20) 171 | 172 | var font = readFontSvg("fonts/DejaVuSans.svg") 173 | font.size = 11 # 11px or 8pt 174 | var glyph = font.typeface.glyphs["g"] 175 | 176 | for i in 0 ..< 10: 177 | var glyphOffset: Vec2 178 | var at = vec2(12.0 + float(i)*12, 11) 179 | var glyphImage = font.getGlyphImage( 180 | glyph, 181 | glyphOffset, 182 | quality = 4, 183 | subPixelShift = float(i)/10.0 184 | ) 185 | image.draw( 186 | glyphImage, 187 | translate(at + glyphOffset) 188 | ) 189 | 190 | let mag = 6.0 191 | image = image.magnifyNearest(mag.int) 192 | for i in 0..<10: 193 | let at = vec2(12.0 + float(i)*12, 15) * mag 194 | font.drawText(image, at + vec2(0, 6), "+0." & $i) 195 | 196 | image.alphaToBlackAndWhite() 197 | 198 | let red = rgba(255, 0, 0, 255) 199 | for i in 0..<10: 200 | let at = vec2(12.0 + float(i)*12, 15) * mag 201 | image.strokeRectInner(rect(at + vec2(0, -13 * mag), vec2(7 * mag, 13 * mag)), red) 202 | 203 | image.writeFile("rendered/subpixelglyphs.png") 204 | 205 | # block: 206 | # var font = readFontTtf("fonts/Moon Bold.otf") 207 | # font.size = 300 208 | # font.lineHeight = 300 209 | 210 | # var image = font.getGlyphOutlineImage("Q") 211 | 212 | # echo font.typeface.glyphs["Q"].pathStr 213 | 214 | # image.writeFile("rendered/qOutLine.png") 215 | 216 | # image = font.getGlyphImage("Q") 217 | # image.alphaToBlackAndWhite() 218 | # image.writeFile("rendered/qFill.png") 219 | 220 | block: 221 | var image = newImage(200, 100) 222 | 223 | var font = readFontSvg("fonts/Ubuntu.svg") 224 | font.size = 11 # 11px or 8pt 225 | font.lineHeight = 20 226 | 227 | # compute layout 228 | var layout = font.typeset(""" 229 | Two roads diverged in a yellow wood, 230 | And sorry I could not travel both 231 | And be one traveler, long I stood 232 | And looked down one as far as I could 233 | To where it bent in the undergrowth;""") 234 | 235 | echo "textBounds: ", layout.textBounds() 236 | 237 | # draw text at a layout 238 | image.drawText(layout) 239 | 240 | let mag = 3.0 241 | image = image.magnifyNearest(mag.int) 242 | image.alphaToBlackAndWhite() 243 | 244 | # draw layout boxes 245 | for pos in layout: 246 | var font = pos.font 247 | if pos.character in font.typeface.glyphs: 248 | var glyph = font.typeface.glyphs[pos.character] 249 | var glyphOffset: Vec2 250 | let img = font.getGlyphImage( 251 | glyph, 252 | glyphOffset, 253 | subPixelShift = pos.subPixelShift 254 | ) 255 | image.strokeRectInner( 256 | rect( 257 | (pos.rect.xy + glyphOffset) * mag, 258 | vec2(float img.width, float img.height) * mag 259 | ), 260 | rgba(255, 0, 0, 255) 261 | ) 262 | image.writeFile("rendered/layout.png") 263 | 264 | image.fill(rgba(255, 255, 255, 255)) 265 | # draw layout boxes only 266 | for pos in layout: 267 | var font = pos.font 268 | if pos.character in font.typeface.glyphs: 269 | var glyph = font.typeface.glyphs[pos.character] 270 | var glyphOffset: Vec2 271 | let img = font.getGlyphImage( 272 | glyph, 273 | glyphOffset, 274 | subPixelShift = pos.subPixelShift 275 | ) 276 | image.strokeRectInner( 277 | rect( 278 | (pos.rect.xy + glyphOffset) * mag, 279 | vec2(float img.width, float img.height) * mag 280 | ), 281 | rgba(255, 0, 0, 255) 282 | ) 283 | image.writeFile("rendered/layoutNoText.png") 284 | 285 | block: 286 | var image = newImage(500, 200) 287 | 288 | var font = readFontSvg("fonts/Ubuntu.svg") 289 | font.size = 11 # 11px or 8pt 290 | font.lineHeight = 11 291 | 292 | image.drawText( 293 | font.typeset( 294 | "Left, Top", 295 | pos = vec2(20, 20), 296 | size = vec2(460, 160), 297 | hAlign = Left, 298 | vAlign = Top 299 | ) 300 | ) 301 | image.drawText( 302 | font.typeset( 303 | "Left, Middle", 304 | pos = vec2(20, 20), 305 | size = vec2(460, 160), 306 | hAlign = Left, 307 | vAlign = Middle 308 | ) 309 | ) 310 | image.drawText( 311 | font.typeset( 312 | "Left, Bottom", 313 | pos = vec2(20, 20), 314 | size = vec2(460, 160), 315 | hAlign = Left, 316 | vAlign = Bottom 317 | ) 318 | ) 319 | 320 | image.drawText( 321 | font.typeset( 322 | "Center, Top", 323 | pos = vec2(20, 20), 324 | size = vec2(460, 160), 325 | hAlign = Center, 326 | vAlign = Top 327 | ) 328 | ) 329 | image.drawText( 330 | font.typeset( 331 | "Center, Middle", 332 | pos = vec2(20, 20), 333 | size = vec2(460, 160), 334 | hAlign = Center, 335 | vAlign = Middle 336 | ) 337 | ) 338 | image.drawText( 339 | font.typeset( 340 | "Center, Bottom", 341 | pos = vec2(20, 20), 342 | size = vec2(460, 160), 343 | hAlign = Center, 344 | vAlign = Bottom 345 | ) 346 | ) 347 | 348 | image.drawText( 349 | font.typeset( 350 | "Right, Top", 351 | pos = vec2(20, 20), 352 | size = vec2(460, 160), 353 | hAlign = Right, 354 | vAlign = Top 355 | ) 356 | ) 357 | image.drawText( 358 | font.typeset( 359 | "Right, Middle", 360 | pos = vec2(20, 20), 361 | size = vec2(460, 160), 362 | hAlign = Right, 363 | vAlign = Middle 364 | ) 365 | ) 366 | image.drawText( 367 | font.typeset( 368 | "Right, Bottom", 369 | pos = vec2(20, 20), 370 | size = vec2(460, 160), 371 | hAlign = Right, 372 | vAlign = Bottom 373 | ) 374 | ) 375 | 376 | image.alphaToBlackAndWhite() 377 | 378 | image.strokeRectInner( 379 | rect(20, 20, 460, 160), 380 | rgba(255, 0, 0, 255) 381 | ) 382 | 383 | image.writeFile("rendered/alignment.png") 384 | 385 | block: 386 | var image = newImage(500, 200) 387 | 388 | var font = readFontSvg("fonts/Ubuntu.svg") 389 | font.size = 16 390 | font.lineHeight = 20 391 | 392 | image.drawText(font.typeset( 393 | readFile("samples/sample.wrap.txt"), 394 | pos = vec2(100, 20), 395 | size = vec2(300, 160) 396 | )) 397 | 398 | image.alphaToBlackAndWhite() 399 | 400 | image.strokeRectInner( 401 | rect(100, 20, 300, 160), 402 | rgba(255, 0, 0, 255) 403 | ) 404 | 405 | image.writeFile("rendered/wordwrap.png") 406 | 407 | block: 408 | var image = newImage(500, 200) 409 | 410 | var font = readFontTtf("fonts/hanazono/HanaMinA.ttf") 411 | font.size = 16 412 | font.lineHeight = 20 413 | 414 | image.drawText(font.typeset( 415 | readFile("samples/sample.ch.txt"), 416 | pos = vec2(100, 20), 417 | size = vec2(300, 160) 418 | )) 419 | 420 | image.alphaToBlackAndWhite() 421 | 422 | image.strokeRectInner( 423 | rect(100, 20, 300, 160), 424 | rgba(255, 0, 0, 255) 425 | ) 426 | 427 | image.writeFile("rendered/wordwrapch.png") 428 | 429 | block: 430 | var image = newImage(300, 120) 431 | 432 | var font = readFontSvg("fonts/Ubuntu.svg") 433 | font.size = 16 # 11px or 8pt 434 | font.lineHeight = 20 435 | 436 | # compute layout 437 | var layout = font.typeset(""" 438 | Two roads diverged in a yellow wood, 439 | And sorry I could not travel both 440 | And be one traveler, long I stood 441 | And looked down one as far as I could 442 | To where it bent in the undergrowth;""", 443 | vec2(10, 10)) 444 | 445 | # draw text at a layout 446 | image.drawText(layout) 447 | 448 | image.alphaToBlackAndWhite() 449 | 450 | let selectionRects = layout.getSelection(23, 120) 451 | # draw selection boxes 452 | for rect in selectionRects: 453 | image.strokeRectInner(rect, rgba(255, 0, 0, 255)) 454 | 455 | image.writeFile("rendered/selection.png") 456 | 457 | block: 458 | 459 | var image = newImage(300, 120) 460 | 461 | var font = readFontSvg("fonts/Ubuntu.svg") 462 | font.size = 16 463 | font.lineHeight = 20 464 | 465 | # compute layout 466 | var layout = font.typeset(""" 467 | Two roads diverged in a yellow wood, 468 | And sorry I could not travel both 469 | And be one traveler, long I stood 470 | And looked down one as far as I could 471 | To where it bent in the undergrowth;""", 472 | vec2(10, 10)) 473 | 474 | # draw text at a layout 475 | image.drawText(layout) 476 | 477 | image.alphaToBlackAndWhite() 478 | 479 | let at = vec2(120, 52) 480 | let g = layout.pickGlyphAt(at) 481 | # draw g 482 | image.strokeRectInner(rect(at, vec2(4, 4)), rgba(0, 0, 255, 255)) 483 | image.strokeRectInner(g.selectRect, rgba(255, 0, 0, 255)) 484 | 485 | image.writeFile("rendered/picking.png") 486 | 487 | block: 488 | var image = newImage(500, 120) 489 | 490 | var font = readFontSvg("fonts/Ubuntu.svg") 491 | font.size = 16 492 | font.lineHeight = 20 493 | 494 | # compute layout 495 | var layout = font.typeset( 496 | "name\tstate\tnumber\tcount\n" & 497 | "cat\tT\t3.14\t0\n" & 498 | "dog\tF\t2.11\t1\n" & 499 | "really loong cat\tG\t123.678\t2", 500 | vec2(10, 10), 501 | tabWidth = 100) 502 | 503 | # draw text at a layout 504 | image.drawText(layout) 505 | image.alphaToBlackAndWhite() 506 | image.writeFile("rendered/tabs.png") 507 | 508 | block: 509 | 510 | var mainFont = readFontTtf("fonts/Ubuntu.ttf") 511 | 512 | var image = newImage(1200, 400) 513 | var y = 10.0 514 | for fontPath in [ 515 | "fonts/Jura-Regular.ttf", 516 | "fonts/IBMPlexSans-Regular.ttf", 517 | "fonts/silver.ttf", 518 | "fonts/Ubuntu.ttf", 519 | "fonts/Lato-Regular.ttf", 520 | "fonts/SourceSansPro-Regular.ttf", 521 | "fonts/Changa-Bold.ttf" 522 | ]: 523 | mainFont.size = 10 524 | mainFont.lineHeight = 32 525 | mainFont.drawText(image, vec2(10, y), fontPath.lastPathPart) 526 | 527 | var x = 150.0 528 | var font = readFontTtf(fontPath) 529 | print font.typeface.name, font.typeface.ascent, font.typeface.descent, font.typeface.xHeight, font.typeface.capHeight 530 | for s in [8, 12, 16, 18, 20, 32]: 531 | font.size = s.float 532 | font.lineHeight = 32 533 | 534 | let fontHeight = font.typeface.ascent - font.typeface.descent 535 | print fontHeight / font.size , font.typeface.unitsPerEm / font.size 536 | # print font.ascent * scale, font.descent * scale 537 | font.drawText(image, vec2(x, y), "Figte") 538 | image.strokeRectInner(rect(x, y, 100, 32), rgba(255, 255, 255, 255)) 539 | 540 | x += 150 541 | 542 | y += 50 543 | 544 | let (outp, _) = execCmdEx("git diff tests/rendered/*.png") 545 | if len(outp) != 0: 546 | echo outp 547 | quit("Output does not match") 548 | -------------------------------------------------------------------------------- /tests/test_char.nim: -------------------------------------------------------------------------------- 1 | import pixie, print, tables, typography, vmath, os, osproc 2 | 3 | setCurrentDir(getCurrentDir() / "tests") 4 | 5 | block: 6 | var font = readFontTtf("fonts/Changa-Bold.ttf") 7 | #var font = readFontTtf("fonts/Ubuntu.ttf") 8 | #var font = readFontTtf("fonts/Moon Bold.otf") 9 | #var font = readFontOtf("/p/googlefonts/apache/jsmathcmbx10/jsMath-cmbx10.ttf") 10 | font.size = 1000 11 | font.lineHeight = 1000 12 | 13 | # for name in font.glyphs.keys: 14 | # font.glyphs[name].name = name 15 | 16 | for i, glyph in font.typeface.glyphArr: 17 | #print glyph.code 18 | if glyph.code != "+": continue 19 | print i, glyph.code 20 | #if name != "a": continue 21 | 22 | print glyph.code in font.typeface.glyphs 23 | if glyph.code in font.typeface.glyphs: 24 | 25 | print glyph 26 | var g = glyph 27 | g.parseGlyph(font) 28 | print g 29 | 30 | # g.commands = g.commands[0 .. 20] 31 | # g.commands.add PathCommand(kind: End) 32 | 33 | #print font.glyphs[name].ttfOffset 34 | #print font.glyphs[name].commands.len 35 | # for j, command in font.typeface.glyphs[glyph.code].path.commands: 36 | # echo j, ": ", command 37 | # for i in 0 ..< command.numbers.len div 2: 38 | # var x = int command.numbers[i*2+0] 39 | # var y = int command.numbers[i*2+1] 40 | #print x, y 41 | 42 | glyph.commandsToShapes() 43 | 44 | print glyph.shapes.len 45 | for i, shape in glyph.shapes: 46 | for j, segment in shape: 47 | print i, j, segment 48 | 49 | #print glyph 50 | 51 | # var image = font.getGlyphOutlineImage( 52 | # glyph.code, 53 | # lines=true, 54 | # points=true, 55 | # winding=true 56 | # ) 57 | 58 | # image.writeFile("rendered/test_char.png") 59 | 60 | # var glyphOffset: Vec2 61 | # image = font.getGlyphImage(glyph, glyphOffset, quality=4) 62 | # image.alphaToBlackAndWhite() 63 | # image.writeFile("rendered/test_charFill.png") 64 | 65 | # if image.width == 1396: 66 | # quit() 67 | 68 | let (outp, _) = execCmdEx("git diff tests/rendered/*.png") 69 | if len(outp) != 0: 70 | echo outp 71 | quit("Output does not match") 72 | -------------------------------------------------------------------------------- /tests/test_otf.nim: -------------------------------------------------------------------------------- 1 | import chroma, pixie, print, tables, typography, typography, vmath, json, os, osproc 2 | 3 | setCurrentDir(getCurrentDir() / "tests") 4 | 5 | block: 6 | var font = readFontOtf("fonts/Changa-Bold.ttf") 7 | #var font = readFontOtf("/p/googlefonts/ofl/changa/static/Changa-Regular.ttf") 8 | #var font = readFontOtf("fonts/Ubuntu.ttf") 9 | #var font = readFontOtf("fonts/hanazono/HanaMinA.ttf") 10 | #var font = readFontOtf("/p/googlefonts/apache/jsmathcmbx10/jsMath-cmbx10.ttf") 11 | #var font = readFontOtf("/p/googlefonts/apache/nokora/Nokora-Bold.ttf") 12 | #var font = readFontOtf("/p/googlefonts/ofl/arvo/Arvo-Regular.ttf") # zero sides path 13 | #var font = readFontOtf("/p/googlefonts/ofl/chelseamarket/ChelseaMarket-Regular.ttf") # negative sided image 14 | #var font = readFontOtf("/p/googlefonts/ofl/frijole/Frijole-Regular.ttf") # negative sized glyph 15 | 16 | font.size = 40 17 | font.lineHeight = 40 18 | print font.typeface.unitsPerEm 19 | #font.unitsPerEm = 2000 20 | print font.typeface.ascent 21 | print font.typeface.descent 22 | print font.typeface.lineGap 23 | 24 | echo pretty %font.typeface.otf.os2 25 | #for glyph in font.glyphArr: 26 | # print glyph.code, glyph.advance 27 | 28 | var image = newImage(500, 40) 29 | let ctx = newContext(image) 30 | ctx.strokeStyle = rgba(0,0,0,255) 31 | ctx.strokeSegment(segment(vec2(0, font.capline) + vec2(0, 0.25), vec2(500, font.capline) + vec2(0, 0.25))) 32 | ctx.strokeSegment(segment(vec2(0, font.baseline) + vec2(0, 0.25), vec2(500, font.baseline) + vec2(0, 0.25))) 33 | 34 | var text = """+Welcome, Earthling.""" 35 | if "q" notin font.typeface.glyphs: 36 | # can't display english, use random glpyhs: 37 | text = "" 38 | var i = 0 39 | for g in font.typeface.glyphs.values: 40 | text.add(g.code) 41 | inc i 42 | if i > 80: 43 | break 44 | 45 | font.drawText( 46 | image, 47 | vec2(10, 0), 48 | text 49 | ) 50 | 51 | image.alphaToBlackAndWhite() 52 | image.writeFile("rendered/test_otf.png") 53 | 54 | echo "saved" 55 | 56 | let (outp, _) = execCmdEx("git diff tests/rendered/*.png") 57 | if len(outp) != 0: 58 | echo outp 59 | quit("Output does not match") 60 | -------------------------------------------------------------------------------- /tests/test_svg.nim: -------------------------------------------------------------------------------- 1 | import pixie, print, tables, typography, typography, vmath, os, osproc, 2 | typography/svgfont 3 | 4 | setCurrentDir(getCurrentDir() / "tests") 5 | 6 | block: 7 | var font = readFontSvg("fonts/Changa-Bold.svg") 8 | 9 | font.size = 40 10 | font.lineHeight = 40 11 | print font.typeface.unitsPerEm 12 | #font.unitsPerEm = 2000 13 | print font.typeface.ascent 14 | print font.typeface.descent 15 | print font.typeface.lineGap 16 | 17 | #echo pretty %font.otf.os2 18 | #for glyph in font.glyphArr: 19 | # print glyph.code, glyph.advance 20 | 21 | var image = newImage(500, 40) 22 | 23 | var text = """+Welcome, Earthling.""" 24 | if "q" notin font.typeface.glyphs: 25 | # can't display english, use random glpyhs: 26 | text = "" 27 | var i = 0 28 | for g in font.typeface.glyphs.values: 29 | text.add(g.code) 30 | inc i 31 | if i > 80: 32 | break 33 | 34 | font.drawText( 35 | image, 36 | vec2(10, 0), 37 | text 38 | ) 39 | 40 | image.alphaToBlackAndWhite() 41 | image.writeFile("rendered/test_svg.png") 42 | 43 | echo "saved" 44 | 45 | let (outp, _) = execCmdEx("git diff tests/rendered/*.png") 46 | if len(outp) != 0: 47 | echo outp 48 | quit("Output does not match") 49 | -------------------------------------------------------------------------------- /tests/test_textboxes.nim: -------------------------------------------------------------------------------- 1 | import bumpy, chroma, pixie, print, strformat, typography, typography/textboxes, 2 | unicode, vmath, os, osproc, typography/svgfont, common 3 | 4 | setCurrentDir(getCurrentDir() / "tests") 5 | 6 | proc alphaWhite(image: var Image) = 7 | ## Typography deals mostly with transparent images with white text 8 | ## This is hard to see in tests so we convert it to white background 9 | ## with black text. 10 | for x in 0..") 214 | textBox.draw("textbox/paste_1.png") 215 | 216 | block: 217 | print "cut" 218 | var textBox = newTextBox(font, 300, 120, """ 219 | Lorem ipsum dolor sit amet, consectetur elit. Maecenas facilisis massa ac ipsum eff! 220 | Mauris vel turpis a elit scelerisque luctus. Aliquam quam odio, tempor a facilisis et, cursus nec nibh. Nunc ut facilisis arcu. Cras odio lorem, facilisis eget tincidunt nec, maximus sit amet nulla. Nam mi dolor, dignissim ac eleifend ut, malesuada et libero. Vestibulum mattis bibendum mattis. Donec diam odio, pellentesque sed bibendum quis, facilisis ut mauris.""") 221 | textBox.mouseAction(vec2(30, 30), click = true) 222 | textBox.mouseAction(vec2(230, 30), click = false) 223 | print textBox.cut() 224 | textBox.draw("textbox/cut.png") 225 | 226 | block: 227 | print "select word" 228 | var textBox = newTextBox(font, 300, 120, """ 229 | Lorem ipsum dolor sit amet, consectetur elit. Maecenas facilisis massa ac ipsum eff! 230 | Mauris vel turpis a elit scelerisque luctus. Aliquam quam odio, tempor a facilisis et, cursus nec nibh. Nunc ut facilisis arcu. Cras odio lorem, facilisis eget tincidunt nec, maximus sit amet nulla. Nam mi dolor, dignissim ac eleifend ut, malesuada et libero. Vestibulum mattis bibendum mattis. Donec diam odio, pellentesque sed bibendum quis, facilisis ut mauris.""") 231 | textBox.selectWord(vec2(30, 30)) 232 | textBox.draw("textbox/select_word.png") 233 | 234 | block: 235 | print "select peragraph" 236 | var textBox = newTextBox(font, 300, 120, """ 237 | Lorem ipsum dolor sit amet, consectetur elit. Maecenas facilisis massa ac ipsum eff! 238 | Mauris vel turpis a elit scelerisque luctus. Aliquam quam odio, tempor a facilisis et, cursus nec nibh. Nunc ut facilisis arcu. Cras odio lorem, facilisis eget tincidunt nec, maximus sit amet nulla. Nam mi dolor, dignissim ac eleifend ut, malesuada et libero. Vestibulum mattis bibendum mattis. Donec diam odio, pellentesque sed bibendum quis, facilisis ut mauris.""") 239 | textBox.selectParagraph(vec2(30, 30)) 240 | textBox.draw("textbox/select_peragraph.png") 241 | 242 | block: 243 | print "select all" 244 | var textBox = newTextBox(font, 300, 120, """ 245 | Lorem ipsum dolor sit amet, consectetur elit. Maecenas facilisis massa ac ipsum eff! 246 | Mauris vel turpis a elit scelerisque luctus. Aliquam quam odio, tempor a facilisis et, cursus nec nibh. Nunc ut facilisis arcu. Cras odio lorem, facilisis eget tincidunt nec, maximus sit amet nulla. Nam mi dolor, dignissim ac eleifend ut, malesuada et libero. Vestibulum mattis bibendum mattis. Donec diam odio, pellentesque sed bibendum quis, facilisis ut mauris.""") 247 | textBox.selectAll() 248 | textBox.draw("textbox/select_all.png") 249 | 250 | block: 251 | print "jump to 0 on up" 252 | var textBox = newTextBox(font, 300, 120, """ 253 | Lorem ipsum dolor sit amet, consectetur elit. Maecenas facilisis massa ac ipsum eff! 254 | Mauris vel turpis a elit scelerisque luctus. Aliquam quam odio, tempor a facilisis et, cursus nec nibh. Nunc ut facilisis arcu. Cras odio lorem, facilisis eget tincidunt nec, maximus sit amet nulla. Nam mi dolor, dignissim ac eleifend ut, malesuada et libero. Vestibulum mattis bibendum mattis. Donec diam odio, pellentesque sed bibendum quis, facilisis ut mauris.""") 255 | textBox.mouseAction(vec2(30, 30), click = true) 256 | textBox.draw("textbox/jump_up_0.png") 257 | textBox.up() 258 | textBox.draw("textbox/jump_up_1.png") 259 | textBox.up() 260 | textBox.draw("textbox/jump_up_2.png") 261 | textBox.down() 262 | textBox.draw("textbox/jump_up_3.png") 263 | 264 | block: 265 | print "jump to last on down" 266 | var textBox = newTextBox(font, 300, 120, """ 267 | Lorem ipsum dolor sit amet, consectetur elit. Maecenas facilisis quam odio, tempor a facilisis massa ac ipsum eff!""") 268 | textBox.mouseAction(vec2(30, 30), click = true) 269 | textBox.draw("textbox/jump_down_0.png") 270 | textBox.down() 271 | textBox.draw("textbox/jump_down_1.png") 272 | textBox.down() 273 | textBox.draw("textbox/jump_down_2.png") 274 | textBox.up() 275 | textBox.draw("textbox/jump_down_3.png") 276 | 277 | block: 278 | print "scrolling up & down" 279 | var textBox = newTextBox(font, 300, 120, """ 280 | Lorem ipsum dolor sit amet, consectetur elit. 281 | 282 | Maecenas facilisis massa ac ipsum efficitur, in consequat justo imperdiet. 283 | 284 | Mauris vel turpis a elit scelerisque luctus. Aliquam quam odio, tempor a facilisis et, cursus nec nibh. Nunc ut facilisis arcu. Cras odio lorem, facilisis eget tincidunt nec, maximus sit amet nulla. Nam mi dolor, dignissim ac eleifend ut, malesuada et libero. Vestibulum mattis bibendum mattis. Donec diam odio, pellentesque sed bibendum quis, facilisis ut mauris.""") 285 | textBox.setCursor(0) 286 | for i in 0..10: 287 | textBox.right() 288 | var f = 0 289 | textBox.draw(&"textbox/scroll_{f}.png") 290 | inc f 291 | for i in 0..10: 292 | textBox.down() 293 | textBox.draw(&"textbox/scroll_{f}.png") 294 | inc f 295 | for i in 0..10: 296 | textBox.up() 297 | textBox.draw(&"textbox/scroll_{f}.png") 298 | inc f 299 | 300 | let (outp, _) = execCmdEx("git diff tests/*.png") 301 | if len(outp) != 0: 302 | echo outp 303 | quit("Output does not match") 304 | -------------------------------------------------------------------------------- /tests/test_ttf.nim: -------------------------------------------------------------------------------- 1 | import pixie, print, typography, vmath, os, osproc 2 | 3 | setCurrentDir(getCurrentDir() / "tests") 4 | 5 | block: 6 | #var font = readFontTtf("fonts/Changa-Bold.ttf") 7 | #var font = readFontTtf("fonts/Ubuntu.ttf") 8 | var font = readFontTtf("fonts/hanazono/HanaMinA.ttf") 9 | font.size = 20 10 | font.lineHeight = 40 11 | print font.typeface.unitsPerEm 12 | print font.typeface.ascent 13 | print font.typeface.descent 14 | print font.typeface.lineGap 15 | 16 | var image = newImage(500, 40) 17 | 18 | font.drawText( 19 | image, 20 | vec2(10, 0), 21 | """!!! The "quick" brown fox jumps over the lazy dog. !!!""" 22 | ) 23 | 24 | image.alphaToBlackAndWhite() 25 | image.writeFile("rendered/test_ttf.png") 26 | 27 | echo "saved" 28 | 29 | let (outp, _) = execCmdEx("git diff tests/rendered/*.png") 30 | if len(outp) != 0: 31 | echo outp 32 | quit("Output does not match") 33 | -------------------------------------------------------------------------------- /tests/textbox/backspace_delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/backspace_delete.png -------------------------------------------------------------------------------- /tests/textbox/copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/copy.png -------------------------------------------------------------------------------- /tests/textbox/cut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/cut.png -------------------------------------------------------------------------------- /tests/textbox/jump_down_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/jump_down_0.png -------------------------------------------------------------------------------- /tests/textbox/jump_down_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/jump_down_1.png -------------------------------------------------------------------------------- /tests/textbox/jump_down_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/jump_down_2.png -------------------------------------------------------------------------------- /tests/textbox/jump_down_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/jump_down_3.png -------------------------------------------------------------------------------- /tests/textbox/jump_up_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/jump_up_0.png -------------------------------------------------------------------------------- /tests/textbox/jump_up_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/jump_up_1.png -------------------------------------------------------------------------------- /tests/textbox/jump_up_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/jump_up_2.png -------------------------------------------------------------------------------- /tests/textbox/jump_up_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/jump_up_3.png -------------------------------------------------------------------------------- /tests/textbox/left_right.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/left_right.gif -------------------------------------------------------------------------------- /tests/textbox/left_right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/left_right.png -------------------------------------------------------------------------------- /tests/textbox/left_right_shift_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/left_right_shift_0.png -------------------------------------------------------------------------------- /tests/textbox/left_right_shift_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/left_right_shift_1.png -------------------------------------------------------------------------------- /tests/textbox/left_right_shift_10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/left_right_shift_10.png -------------------------------------------------------------------------------- /tests/textbox/left_right_shift_11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/left_right_shift_11.png -------------------------------------------------------------------------------- /tests/textbox/left_right_shift_12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/left_right_shift_12.png -------------------------------------------------------------------------------- /tests/textbox/left_right_shift_13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/left_right_shift_13.png -------------------------------------------------------------------------------- /tests/textbox/left_right_shift_14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/left_right_shift_14.png -------------------------------------------------------------------------------- /tests/textbox/left_right_shift_15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/left_right_shift_15.png -------------------------------------------------------------------------------- /tests/textbox/left_right_shift_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/left_right_shift_16.png -------------------------------------------------------------------------------- /tests/textbox/left_right_shift_17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/left_right_shift_17.png -------------------------------------------------------------------------------- /tests/textbox/left_right_shift_18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/left_right_shift_18.png -------------------------------------------------------------------------------- /tests/textbox/left_right_shift_19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/left_right_shift_19.png -------------------------------------------------------------------------------- /tests/textbox/left_right_shift_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/left_right_shift_2.png -------------------------------------------------------------------------------- /tests/textbox/left_right_shift_20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/left_right_shift_20.png -------------------------------------------------------------------------------- /tests/textbox/left_right_shift_21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/left_right_shift_21.png -------------------------------------------------------------------------------- /tests/textbox/left_right_shift_22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/left_right_shift_22.png -------------------------------------------------------------------------------- /tests/textbox/left_right_shift_23.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/left_right_shift_23.png -------------------------------------------------------------------------------- /tests/textbox/left_right_shift_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/left_right_shift_3.png -------------------------------------------------------------------------------- /tests/textbox/left_right_shift_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/left_right_shift_4.png -------------------------------------------------------------------------------- /tests/textbox/left_right_shift_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/left_right_shift_5.png -------------------------------------------------------------------------------- /tests/textbox/left_right_shift_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/left_right_shift_6.png -------------------------------------------------------------------------------- /tests/textbox/left_right_shift_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/left_right_shift_7.png -------------------------------------------------------------------------------- /tests/textbox/left_right_shift_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/left_right_shift_8.png -------------------------------------------------------------------------------- /tests/textbox/left_right_shift_9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/left_right_shift_9.png -------------------------------------------------------------------------------- /tests/textbox/paste_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/paste_0.png -------------------------------------------------------------------------------- /tests/textbox/paste_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/paste_1.png -------------------------------------------------------------------------------- /tests/textbox/picking_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/picking_0.png -------------------------------------------------------------------------------- /tests/textbox/picking_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/picking_1.png -------------------------------------------------------------------------------- /tests/textbox/picking_10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/picking_10.png -------------------------------------------------------------------------------- /tests/textbox/picking_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/picking_2.png -------------------------------------------------------------------------------- /tests/textbox/picking_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/picking_3.png -------------------------------------------------------------------------------- /tests/textbox/picking_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/picking_4.png -------------------------------------------------------------------------------- /tests/textbox/picking_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/picking_5.png -------------------------------------------------------------------------------- /tests/textbox/picking_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/picking_6.png -------------------------------------------------------------------------------- /tests/textbox/picking_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/picking_7.png -------------------------------------------------------------------------------- /tests/textbox/picking_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/picking_8.png -------------------------------------------------------------------------------- /tests/textbox/picking_9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/picking_9.png -------------------------------------------------------------------------------- /tests/textbox/plain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/plain.png -------------------------------------------------------------------------------- /tests/textbox/scroll_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/scroll_0.png -------------------------------------------------------------------------------- /tests/textbox/scroll_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/scroll_1.png -------------------------------------------------------------------------------- /tests/textbox/scroll_10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/scroll_10.png -------------------------------------------------------------------------------- /tests/textbox/scroll_11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/scroll_11.png -------------------------------------------------------------------------------- /tests/textbox/scroll_12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/scroll_12.png -------------------------------------------------------------------------------- /tests/textbox/scroll_13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/scroll_13.png -------------------------------------------------------------------------------- /tests/textbox/scroll_14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/scroll_14.png -------------------------------------------------------------------------------- /tests/textbox/scroll_15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/scroll_15.png -------------------------------------------------------------------------------- /tests/textbox/scroll_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/scroll_16.png -------------------------------------------------------------------------------- /tests/textbox/scroll_17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/scroll_17.png -------------------------------------------------------------------------------- /tests/textbox/scroll_18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/scroll_18.png -------------------------------------------------------------------------------- /tests/textbox/scroll_19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/scroll_19.png -------------------------------------------------------------------------------- /tests/textbox/scroll_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/scroll_2.png -------------------------------------------------------------------------------- /tests/textbox/scroll_20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/scroll_20.png -------------------------------------------------------------------------------- /tests/textbox/scroll_21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/scroll_21.png -------------------------------------------------------------------------------- /tests/textbox/scroll_22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/scroll_22.png -------------------------------------------------------------------------------- /tests/textbox/scroll_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/scroll_3.png -------------------------------------------------------------------------------- /tests/textbox/scroll_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/scroll_4.png -------------------------------------------------------------------------------- /tests/textbox/scroll_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/scroll_5.png -------------------------------------------------------------------------------- /tests/textbox/scroll_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/scroll_6.png -------------------------------------------------------------------------------- /tests/textbox/scroll_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/scroll_7.png -------------------------------------------------------------------------------- /tests/textbox/scroll_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/scroll_8.png -------------------------------------------------------------------------------- /tests/textbox/scroll_9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/scroll_9.png -------------------------------------------------------------------------------- /tests/textbox/select.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/select.gif -------------------------------------------------------------------------------- /tests/textbox/select_all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/select_all.png -------------------------------------------------------------------------------- /tests/textbox/select_peragraph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/select_peragraph.png -------------------------------------------------------------------------------- /tests/textbox/select_word.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/select_word.png -------------------------------------------------------------------------------- /tests/textbox/typing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/typing.png -------------------------------------------------------------------------------- /tests/textbox/up_down_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/up_down_0.png -------------------------------------------------------------------------------- /tests/textbox/up_down_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/up_down_1.png -------------------------------------------------------------------------------- /tests/textbox/up_down_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/up_down_2.png -------------------------------------------------------------------------------- /tests/textbox/up_down_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/up_down_3.png -------------------------------------------------------------------------------- /tests/textbox/up_down_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/up_down_4.png -------------------------------------------------------------------------------- /tests/textbox/up_down_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/up_down_5.png -------------------------------------------------------------------------------- /tests/textbox/up_down_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/up_down_6.png -------------------------------------------------------------------------------- /tests/textbox/up_down_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/up_down_7.png -------------------------------------------------------------------------------- /tests/textbox/up_down_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/up_down_8.png -------------------------------------------------------------------------------- /tests/textbox/up_down_9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/up_down_9.png -------------------------------------------------------------------------------- /tests/textbox/word_backspace_delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treeform/typography/a2a5165c36e0098dea526712890fb7e988ba27f2/tests/textbox/word_backspace_delete.png -------------------------------------------------------------------------------- /typography.nimble: -------------------------------------------------------------------------------- 1 | version = "0.7.14" 2 | author = "Andre von Houck" 3 | description = "Fonts, Typesetting and Rasterization for Nim." 4 | license = "MIT" 5 | srcDir = "src" 6 | 7 | requires "nim >= 1.4.0" 8 | requires "pixie >= 5.0.1" 9 | requires "print >= 0.1.0" 10 | 11 | task docs, "Generate API documents": 12 | exec "nimble doc --index:on --project --out:docs --hints:off src/typography.nim" 13 | --------------------------------------------------------------------------------