├── .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 | [](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 | 
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 | 
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 | 
65 |
66 | ```nim
67 | font.drawText(image, vec2(10, 10), readFile("examples/sample.ru.txt"))
68 | ```
69 | 
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 | 
89 |
90 | Most of the time you would like to just get the image instead:
91 |
92 | ```nim
93 | font.getGlyphImage("Q")
94 | ```
95 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
208 |
209 | [Apple Core Text](https://developer.apple.com/documentation/coretext) renderer (4x):
210 |
211 | 
212 |
213 | [Paint.net](Paint.net) renderer (4x):
214 |
215 | 
216 |
217 | [Bohemian Sketch](https://www.sketch.com/) renderer (4x):
218 |
219 | 
220 |
221 | [Window ClearType](https://docs.microsoft.com/en-us/typography/cleartype/) renderer (4x):
222 |
223 | 
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 | 
232 |
233 | Then everything changed. Today our pixels small and they don't follow a typical CRT or LCD orientation.
234 |
235 | 
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 | 
266 |
267 | 
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 |
79 |
80 |
81 |
82 |
Dark Mode
83 |
84 |
88 |
89 | Search:
91 |
92 |
93 | Group by:
94 |
95 | Section
96 | Type
97 |
98 |
99 |
100 |
101 | Imports
102 |
105 |
106 |
107 | Exports
108 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
125 |
126 |
127 |
128 | sizePr= , RVLine , Move , PathCommand , glyphPathToCommands , Quad , typography/font , VLine , capline , HLine , Line , baseline , PathCommandKind , End , RCubic , RLine , SCurve , Font , Typeface , RSCurve , RTQuad , TQuad , sizeEm , RMove , Segment , commandsToShapes , Cubic , RHLine , letterHeight , intersects , sizeEm= , Glyph , RQuad , sizePr , sizePt= , scale , sizePt , Start , GlyphPosition , Left , Center , drawText , Right , textBounds , kerningAdjustment , pickGlyphAt , VAlignMode , getSelection , HAlignMode , Bottom , Middle , typeset , drawText , textBounds , typography/layout , Top , normalLineHeight , Span , typeset , readUInt8 , readNameTable , readFontTtf , readOS2Table , typography/opentype/parser , readUInt32 , readFontTtf , readLongDateTime , readMaxpTable , readInt16 , readLocaTable , readInt32 , readInt8 , readFixed32 , readHheaTable , readInt16Seq , readHmtxTable , readString , readKernTable , parseGlyph , readFontOtf , readCmapTable , readGlyfTable , readUInt16Seq , readUInt16 , % , readFixed16 , fromUTF16BE , readFontOtf , read , getGlyphImage , drawGlyph , makeReady , getGlyphImage , getGlyphOutlineImage , getGlyphImageOffset , alphaToBlankAndWhite , getGlyphSize , getGlyphImage , typography/rasterizer , readFontSvg , readFontSvg , typography/svg
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 | Made with Nim. Generated: 2020-10-25 18:43:20 UTC
139 |
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 |
79 |
80 |
81 |
82 |
Dark Mode
83 |
84 |
88 |
89 | Search:
91 |
92 |
93 | Group by:
94 |
95 | Section
96 | Type
97 |
98 |
99 |
100 |
101 | Imports
102 |
105 |
106 |
107 | Procs
108 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 | font
128 |
129 |
130 |
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 |
158 |
159 |
160 | Made with Nim. Generated: 2020-10-25 18:43:20 UTC
161 |
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 |
--------------------------------------------------------------------------------