diff --git a/fontland/.github/workflows/ci.yml b/fontland/.github/workflows/ci.yml new file mode 100644 index 00000000..8c861685 --- /dev/null +++ b/fontland/.github/workflows/ci.yml @@ -0,0 +1,35 @@ +name: CI + +on: [push, pull_request] + +jobs: + run: + name: "Build using Racket '${{ matrix.racket-version }}' (${{ matrix.racket-variant }})" + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + racket-version: ["7.1", "7.2", "7.3", "7.4", "7.5", "7.6", "7.7", "7.8", "7.9", "current"] + racket-variant: ["BC", "CS"] + # CS builds are only provided for versions 7.4 and up so avoid + # running the job for prior versions. + exclude: + - {racket-version: "7.1", racket-variant: "CS"} + - {racket-version: "7.2", racket-variant: "CS"} + - {racket-version: "7.3", racket-variant: "CS"} + + steps: + - name: Checkout + uses: actions/checkout@master + + - uses: Bogdanp/setup-racket@v0.11 + with: + distribution: 'full' + version: ${{ matrix.racket-version }} + variant: ${{ matrix.racket-variant }} + + - name: Install package and its dependencies + run: raco pkg install --auto --batch + + - name: Run the tests + run: xvfb-run raco test -j 4 -p fontland diff --git a/fontland/.gitignore b/fontland/.gitignore new file mode 100644 index 00000000..720788e3 --- /dev/null +++ b/fontland/.gitignore @@ -0,0 +1,21 @@ +# for Racket +compiled/ +*~ + +# for Mac OS X +.DS_Store +.AppleDouble +.LSOverride +Icon + +# Thumbnails +._* + +# Files that might appear on external disk +.Spotlight-V100 +.Trashes +doc/* +scribblings/*.css +scribblings/*.js +scribblings/*.html +fontland/*.sqlite diff --git a/fontland/LICENSE.md b/fontland/LICENSE.md new file mode 100644 index 00000000..a24d23fa --- /dev/null +++ b/fontland/LICENSE.md @@ -0,0 +1,9 @@ +MIT License for `fontland` + +© 2017-2019 Matthew Butterick + +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: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +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. diff --git a/fontland/README.md b/fontland/README.md new file mode 100644 index 00000000..1790043e --- /dev/null +++ b/fontland/README.md @@ -0,0 +1,16 @@ +## fontland ![Build Status](https://github.com/mbutterick/fontland/workflows/CI/badge.svg) + +Racket library for messing with fonts. Based on [`fontkit`](https://github.com/foliojs/fontkit). + + +## License + + +MIT (code) + +Fonts in `assets` folder are under separate licenses included there. + + +## Project status + +Actively developed, but this package exists entirely to support [`pitfall`](https://github.com/mbutterick/pitfall) and [`quad`](https://github.com/mbutterick/quad). I have no ambition to make this a standalone library with documentation. I add things opportunistically based on what’s happening with Quad. diff --git a/fontland/SUBLICENSE.md b/fontland/SUBLICENSE.md new file mode 100644 index 00000000..2eb26390 --- /dev/null +++ b/fontland/SUBLICENSE.md @@ -0,0 +1,47 @@ +Fontland contains substantial portions of the following software: + +[fontkit](https://github.com/devongovett/fontkit) + +MIT LICENSE +Copyright (c) 2014 Devon Govett + +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: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +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. + + +[racket-fontconfig](https://github.com/takikawa/racket-fontconfig) + +Copyright (c) 2016 Asumu Takikawa + +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: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +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. \ No newline at end of file diff --git a/fontland/fontland/assets/CharterBT License b/fontland/fontland/assets/CharterBT License new file mode 100755 index 00000000..b0308b29 --- /dev/null +++ b/fontland/fontland/assets/CharterBT License @@ -0,0 +1 @@ +This is a copy of the Charter fonts which Bitstream contributed to the X consortium, arranged for use in the MacOS. Here is the copyright notice: (c) Copyright 1989-1992, Bitstream Inc., Cambridge, MA. You are hereby granted permission under all Bitstream propriety rights to use, copy, modify, sublicense, sell, and redistribute the 4 Bitstream Charter (r) Type 1 outline fonts and the 4 Courier Type 1 outline fonts for any purpose and without restriction; provided, that this notice is left intact on all copies of such fonts and that Bitstream's trademark is acknowledged as shown below on all unmodified copies of the 4 Charter Type 1 fonts. BITSTREAM CHARTER is a registered trademark of Bitstream Inc. \ No newline at end of file diff --git a/fontland/fontland/assets/Fira license.txt b/fontland/fontland/assets/Fira license.txt new file mode 100755 index 00000000..37c4a30b --- /dev/null +++ b/fontland/fontland/assets/Fira license.txt @@ -0,0 +1,94 @@ +Digitized data copyright (c) 2012-2015, The Mozilla Foundation and Telefonica S.A. +with Reserved Font Name < Fira >, + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/fontland/fontland/assets/Noto license.txt b/fontland/fontland/assets/Noto license.txt new file mode 100644 index 00000000..d952d62c --- /dev/null +++ b/fontland/fontland/assets/Noto license.txt @@ -0,0 +1,92 @@ +This Font Software is licensed under the SIL Open Font License, +Version 1.1. + +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font +creation efforts of academic and linguistic communities, and to +provide a free and open framework in which fonts may be shared and +improved in partnership with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply to +any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software +components as distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, +deleting, or substituting -- in part or in whole -- any of the +components of the Original Version, by changing formats or by porting +the Font Software to a new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, +modify, redistribute, and sell modified and unmodified copies of the +Font Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, in +Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the +corresponding Copyright Holder. This restriction only applies to the +primary font name as presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created using +the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/fontland/fontland/assets/Noto.ttf b/fontland/fontland/assets/Noto.ttf new file mode 100644 index 00000000..0a01a062 Binary files /dev/null and b/fontland/fontland/assets/Noto.ttf differ diff --git a/fontland/fontland/assets/charter-directory.rktd b/fontland/fontland/assets/charter-directory.rktd new file mode 100644 index 00000000..26c96b55 --- /dev/null +++ b/fontland/fontland/assets/charter-directory.rktd @@ -0,0 +1,32 @@ +((3) + 0 + () + 0 + () + () + (h + ! + () + (tag u . "\u0000\u0001\u0000\u0000") + (rangeShift . 96) + (searchRange . 128) + (numTables . 14) + (tables + h + ! + (equal) + (loca h ! () (tag u . "loca") (offset . 38692) (checkSum . 2795817194) (length . 460)) + (OS/2 h ! () (tag u . "OS/2") (offset . 360) (checkSum . 2351070438) (length . 96)) + (glyf h ! () (tag u . "glyf") (offset . 4620) (checkSum . 1143629849) (length . 34072)) + (hhea h ! () (tag u . "hhea") (offset . 292) (checkSum . 132056097) (length . 36)) + (post h ! () (tag u . "post") (offset . 41520) (checkSum . 1670855689) (length . 514)) + (cvt_ h ! () (tag u . "cvt ") (offset . 4592) (checkSum . 10290865) (length . 26)) + (VDMX h ! () (tag u . "VDMX") (offset . 1372) (checkSum . 1887795202) (length . 1504)) + (prep h ! () (tag u . "prep") (offset . 4512) (checkSum . 490862356) (length . 78)) + (maxp h ! () (tag u . "maxp") (offset . 328) (checkSum . 50135594) (length . 32)) + (hmtx h ! () (tag u . "hmtx") (offset . 456) (checkSum . 3982043058) (length . 916)) + (cmap h ! () (tag u . "cmap") (offset . 2876) (checkSum . 1723761408) (length . 1262)) + (name h ! () (tag u . "name") (offset . 39152) (checkSum . 2629707307) (length . 2367)) + (head h ! () (tag u . "head") (offset . 236) (checkSum . 4281190895) (length . 54)) + (fpgm h ! () (tag u . "fpgm") (offset . 4140) (checkSum . 106535991) (length . 371))) + (entrySelector . 3))) \ No newline at end of file diff --git a/fontland/fontland/assets/charter-italic-directory.rktd b/fontland/fontland/assets/charter-italic-directory.rktd new file mode 100644 index 00000000..0562958f --- /dev/null +++ b/fontland/fontland/assets/charter-italic-directory.rktd @@ -0,0 +1,32 @@ +((3) + 0 + () + 0 + () + () + (h + ! + () + (tag u . "\u0000\u0001\u0000\u0000") + (rangeShift . 96) + (searchRange . 128) + (numTables . 14) + (tables + h + ! + (equal) + (loca h ! () (tag u . "loca") (offset . 37392) (checkSum . 46801904) (length . 460)) + (OS/2 h ! () (tag u . "OS/2") (offset . 360) (checkSum . 2367847603) (length . 96)) + (glyf h ! () (tag u . "glyf") (offset . 4620) (checkSum . 2099535230) (length . 32772)) + (hhea h ! () (tag u . "hhea") (offset . 292) (checkSum . 113838023) (length . 36)) + (post h ! () (tag u . "post") (offset . 40280) (checkSum . 1671576585) (length . 514)) + (cvt_ h ! () (tag u . "cvt ") (offset . 4592) (checkSum . 9307818) (length . 26)) + (VDMX h ! () (tag u . "VDMX") (offset . 1372) (checkSum . 1905948947) (length . 1504)) + (prep h ! () (tag u . "prep") (offset . 4512) (checkSum . 776081685) (length . 78)) + (maxp h ! () (tag u . "maxp") (offset . 328) (checkSum . 50135583) (length . 32)) + (hmtx h ! () (tag u . "hmtx") (offset . 456) (checkSum . 3798537071) (length . 916)) + (cmap h ! () (tag u . "cmap") (offset . 2876) (checkSum . 1723761408) (length . 1262)) + (name h ! () (tag u . "name") (offset . 37852) (checkSum . 2313429994) (length . 2427)) + (head h ! () (tag u . "head") (offset . 236) (checkSum . 4275817075) (length . 54)) + (fpgm h ! () (tag u . "fpgm") (offset . 4140) (checkSum . 106535991) (length . 371))) + (entrySelector . 3))) \ No newline at end of file diff --git a/fontland/fontland/assets/charter-italic.ttf b/fontland/fontland/assets/charter-italic.ttf new file mode 100644 index 00000000..a6501214 Binary files /dev/null and b/fontland/fontland/assets/charter-italic.ttf differ diff --git a/fontland/fontland/assets/charter.otf b/fontland/fontland/assets/charter.otf new file mode 100644 index 00000000..d2314d35 Binary files /dev/null and b/fontland/fontland/assets/charter.otf differ diff --git a/fontland/fontland/assets/charter.ttf b/fontland/fontland/assets/charter.ttf new file mode 100644 index 00000000..33c6d7cb Binary files /dev/null and b/fontland/fontland/assets/charter.ttf differ diff --git a/fontland/fontland/assets/charter.woff b/fontland/fontland/assets/charter.woff new file mode 100755 index 00000000..217aac7a Binary files /dev/null and b/fontland/fontland/assets/charter.woff differ diff --git a/fontland/fontland/assets/fira-otf-directory.rktd b/fontland/fontland/assets/fira-otf-directory.rktd new file mode 100644 index 00000000..8ea7518d --- /dev/null +++ b/fontland/fontland/assets/fira-otf-directory.rktd @@ -0,0 +1,114 @@ +((3) + 0 + () + 0 + () + () + (h + ! + () + (tag . OTTO) + (rangeShift . 64) + (searchRange . 128) + (numTables . 12) + (entrySelector . 3) + (tables + h + ! + (equal) + (maxp + h + ! + () + (tag . maxp) + (length . 6) + (checkSum . 172445696) + (offset . 204)) + (CFF_ + h + ! + () + (tag . |CFF |) + (length . 164604) + (checkSum . 516494622) + (offset . 33472)) + (hhea + h + ! + () + (tag . hhea) + (length . 36) + (checkSum . 101714507) + (offset . 10792)) + (post + h + ! + () + (tag . post) + (length . 32) + (checkSum . 4290248754) + (offset . 33440)) + (GDEF + h + ! + () + (tag . GDEF) + (length . 392) + (checkSum . 936918765) + (offset . 198076)) + (head + h + ! + () + (tag . head) + (length . 54) + (checkSum . 95220183) + (offset . 212)) + (GPOS + h + ! + () + (tag . GPOS) + (length . 78818) + (checkSum . 4027394566) + (offset . 198468)) + (cmap + h + ! + () + (tag . cmap) + (length . 20808) + (checkSum . 3634078005) + (offset . 12632)) + (name + h + ! + () + (tag . name) + (length . 1706) + (checkSum . 2142217964) + (offset . 10924)) + (GSUB + h + ! + () + (tag . GSUB) + (length . 19078) + (checkSum . 1205824848) + (offset . 277288)) + (hmtx + h + ! + () + (tag . hmtx) + (length . 10524) + (checkSum . 2729095083) + (offset . 268)) + (OS/2 + h + ! + () + (tag . OS/2) + (length . 96) + (checkSum . 1618270015) + (offset . 10828))))) diff --git a/fontland/fontland/assets/fira.otf b/fontland/fontland/assets/fira.otf new file mode 100644 index 00000000..20bc9a19 Binary files /dev/null and b/fontland/fontland/assets/fira.otf differ diff --git a/fontland/fontland/assets/fira.ttf b/fontland/fontland/assets/fira.ttf new file mode 100644 index 00000000..4834eede Binary files /dev/null and b/fontland/fontland/assets/fira.ttf differ diff --git a/fontland/fontland/bbox.rkt b/fontland/fontland/bbox.rkt new file mode 100644 index 00000000..14802de5 --- /dev/null +++ b/fontland/fontland/bbox.rkt @@ -0,0 +1,44 @@ +#lang racket/base +(require racket/struct) +(provide (all-defined-out)) + +(struct bbox (min-x min-y max-x max-y) #:transparent #:mutable) + +(define (+bbox [min-x +inf.0] [min-y +inf.0] [max-x -inf.0] [max-y -inf.0]) + (bbox min-x min-y max-x max-y)) + +(define (bbox-width bb) + (unless (bbox? bb) + (raise-argument-error 'bbox-width "bbox" bb)) + (- (bbox-max-x bb) (bbox-min-x bb))) + +(define (bbox-height bb) + (unless (bbox? bb) + (raise-argument-error 'bbox-height "bbox" bb)) + (- (bbox-max-y bb) (bbox-min-y bb))) + +(define (bbox-add-point! bb x y) + (unless (bbox? bb) + (raise-argument-error 'bbox-add-point "bbox" bb)) + (set-bbox-min-x! bb (min x (bbox-min-x bb))) + (set-bbox-min-y! bb (min y (bbox-min-y bb))) + (set-bbox-max-x! bb (max x (bbox-max-x bb))) + (set-bbox-max-y! bb (max y (bbox-max-y bb))) + (void)) + +(define (bbox-copy bb) + (unless (bbox? bb) + (raise-argument-error 'bbox-copy "bbox" bb)) + (apply +bbox (bbox->list bb))) + +(define bbox->list struct->list) + +(module+ test + (require rackunit) + (define bb (+bbox 1 2 4 8)) + (check-equal? (bbox-width bb) 3) + (check-equal? (bbox-height bb) 6) + (bbox-add-point! bb 0 0) + (check-equal? (bbox-width bb) 4) + (check-equal? (bbox-height bb) 8) + (check-equal? (bbox->list (bbox-copy bb)) (bbox->list bb))) diff --git a/fontland/fontland/cff-glyph.rkt b/fontland/fontland/cff-glyph.rkt new file mode 100644 index 00000000..dd2ba97e --- /dev/null +++ b/fontland/fontland/cff-glyph.rkt @@ -0,0 +1,424 @@ +#lang debug racket +(require racket/match + xenomorph + fontland/struct + fontland/table-stream + fontland/table/cff/cff-font + fontland/path + "deque.rkt") +(provide (all-defined-out)) + +#| +approximates +https://github.com/mbutterick/fontkit/blob/master/src/glyph/CFFGlyph.js +|# + +(define (bias this s) + (cond + [(< (vector-length s) 1240) 107] + [(< (vector-length s) 33900) 1131] + [else 32768])) + +(define-syntax-rule (case= ID [(NUMS ...) . BODY] ... [else . ELSEBODY]) + (cond + [(memq ID (list NUMS ...)) . BODY] ... + [else . ELSEBODY])) + +(define (getPath this) + + (define cff (get-table (glyph-font this) 'CFF_)) + (define str (vector-ref (hash-ref* cff 'topDict 'CharStrings) (glyph-id this))) + (define end (+ (index-item-offset str) (index-item-length str))) + (define stream (get-table-stream (glyph-font this) 'CFF_)) + (pos stream (index-item-offset str)) + + (define path (Path)) + + #| + (define (shift deque) (pop-start! deque)) + (define (push deque . vals) (apply push-end! deque vals)) + (define (pop deque) (pop-end! deque)) + (define initialize-stack make-deque) + (define stack-length deque-length) +|# + (define-syntax-rule (shift ID) + (begin0 + (car ID) + (set! ID (cdr ID)))) + + (define-syntax-rule (push ID VAL ...) + (begin + (set! ID (append ID (list VAL ...))) + ID)) + + (define-syntax-rule (pop ID) + (cond + [(> (length ID) 0) + (define-values (head last) (split-at-right ID 1)) + (set! ID head) + (car last)])) + + (define (initialize-stack) null) + (define stack-length length) + + (define stack (initialize-stack)) + (define trans null) + + (define width #false) + (define nStems 0) + (define x 0) + (define y 0) + (define used-gsubrs (make-hasheq)) + (define used-subrs (make-hasheq)) + (define open #false) + + (define gsubrs (hash-ref cff 'globalSubrIndex (vector))) + (define gsubrs-bias (bias this gsubrs)) + + (define privateDict (or (private-dict-for-glyph cff (glyph-id this)) (make-hasheq))) + (define subrs (hash-ref privateDict 'Subrs (vector))) + (define subrs-bias (bias this subrs)) + + (define (check-width) + (unless width + (set! width (+ (shift stack) (hash-ref privateDict 'nominalWidthX))))) + + (define (parse-stems) + (when (odd? (stack-length stack)) + (check-width)) + (set! nStems (+ nStems (arithmetic-shift (stack-length stack) -1))) + (set! stack (initialize-stack)) + 0) + + (define (moveTo x y) + (when open (path-closePath path)) + (path-moveTo path x y) + (set! open #true)) + + (let parse () + (let/ec return + (for ([i (in-naturals)] + #:break (>= (pos stream) end)) + (define op (read-byte stream)) + (cond + [(< op 32) + (case= op + [(1 ;; hstem + 3 ;; vstem + 18 ;; hstemhm + 23) ;; vstemhm + (parse-stems)] + [(4) ;; vmoveto + (when (> (stack-length stack) 1) + (check-width)) + (set! y (+ y (shift stack))) + (moveTo x y)] + [(5) ;; rlineto + (let loop () + (when (>= (stack-length stack) 2) + (set! x (+ x (shift stack))) + (set! y (+ y (shift stack))) + (path-lineTo path x y) + (loop)))] + [(6 ;; hlineto + 7) ;; vlineto + (let loop ([phase (= op 6)]) + (when (>= (stack-length stack) 1) + (if phase + (set! x (+ x (shift stack))) + (set! y (+ y (shift stack)))) + (path-lineTo path x y) + (loop (not phase))))] + [(8) ;; rrcurveto + (let loop () + (when (positive? (stack-length stack)) + (define c1x (+ x (shift stack))) + (define c1y (+ y (shift stack))) + (define c2x (+ c1x (shift stack))) + (define c2y (+ c1y (shift stack))) + (set! x (+ c2x (shift stack))) + (set! y (+ c2y (shift stack))) + (path-bezierCurveTo path c1x c1y c2x c2y x y) + (loop)))] + [(10) ;; callsubr + (define index (+ (pop stack) subrs-bias)) + (define subr (vector-ref subrs index)) + (when subr + (hash-set! used-subrs index #true) + (define p (pos stream)) + (define e end) + (pos stream (index-item-offset subr)) + (set! end (+ (index-item-offset subr) (index-item-length subr))) + (parse) + (pos stream p) + (set! end e))] + [(11) ;; return + (when (< (hash-ref cff 'version) 2) + (return))] + [(14) ;; endchar + (when (< (hash-ref cff 'version) 2) + (when (> (stack-length stack) 0) + (check-width)) + (when open + (path-closePath path) + (set! open #false)))] + [(15) ;; vsindex + (when (< (hash-ref cff 'version) 2) + (error 'vsindex-operator-not-supported))] + [(16) ;; blend + (error 'blend-operator-not-supported)] + [(19 ;; hintmask + 20) ;; cntrmask + (parse-stems) + (pos stream (+ (pos stream) (arithmetic-shift (+ nStems 7) -3)))] + [(21) ;; rmoveto + (when (> (stack-length stack) 2) + (check-width)) + (set! x (+ x (shift stack))) + (set! y (+ y (shift stack))) + (moveTo x y)] + [(22) ;; hmoveto + (when (> (stack-length stack) 1) + (check-width)) + (set! x (+ x (shift stack))) + (moveTo x y)] + [(24) ;; rcurveline + (let loop () + (when (>= (stack-length stack) 8) + (define c1x (+ x (shift stack))) + (define c1y (+ y (shift stack))) + (define c2x (+ c1x (shift stack))) + (define c2y (+ c1y (shift stack))) + (set! x (+ c2x (shift stack))) + (set! y (+ c2y (shift stack))) + (path-bezierCurveTo path c1x c1y c2x c2y x y) + (loop))) + (set! x (+ x (shift stack))) + (set! y (+ y (shift stack))) + (path-lineTo path x y)] + [(25) ;; rlinecurve + (let loop () + (when (>= (stack-length stack) 8) + (set! x (+ x (shift stack))) + (set! y (+ y (shift stack))) + (path-lineTo path x y) + (loop))) + (define c1x (+ x (shift stack))) + (define c1y (+ y (shift stack))) + (define c2x (+ c1x (shift stack))) + (define c2y (+ c1y (shift stack))) + (set! x (+ c2x (shift stack))) + (set! y (+ c2y (shift stack))) + (path-bezierCurveTo path c1x c1y c2x c2y x y)] + [(26) ;; vvcurveto + (when (odd? (stack-length stack)) + (set! x (+ x (shift stack)))) + (let loop () + (when (>= (stack-length stack) 4) + (define c1x x) + (define c1y (+ y (shift stack))) + (define c2x (+ c1x (shift stack))) + (define c2y (+ c1y (shift stack))) + (set! x c2x) + (set! y (+ c2y (shift stack))) + (path-bezierCurveTo path c1x c1y c2x c2y x y) + (loop)))] + [(27) ;; hhcurveto + (when (odd? (stack-length stack)) + (set! y (+ y (shift stack)))) + (let loop () + (when (>= (stack-length stack) 4) + (define c1x (+ x (shift stack))) + (define c1y y) + (define c2x (+ c1x (shift stack))) + (define c2y (+ c1y (shift stack))) + (set! x (+ c2x (shift stack))) + (set! y c2y) + (path-bezierCurveTo path c1x c1y c2x c2y x y) + (loop)))] + [(28) ;; shortint + (push stack (decode int16be stream))] + [(29) ;; callgsubr + (define index (+ (pop stack) gsubrs-bias)) + (define subr (vector-ref gsubrs index)) + (when subr + (hash-set! used-gsubrs index #true) + (define old-pos (pos stream)) + (define old-end end) + (pos stream (index-item-offset subr)) + (set! end (+ (index-item-offset subr) (index-item-length subr))) + (parse) + (pos stream old-pos) + (set! end old-end))] + [(30 ;; vhcurveto + 31) ;; hvcurveto + (let loop ([phase (= op 31)]) + (when (>= (stack-length stack) 4) + (cond + [phase + (define c1x (+ x (shift stack))) + (define c1y y) + (define c2x (+ c1x (shift stack))) + (define c2y (+ c1y (shift stack))) + (set! y (+ c2y (shift stack))) + (set! x (+ c2x (if (= (stack-length stack) 1) (shift stack) 0))) + (path-bezierCurveTo path c1x c1y c2x c2y x y) + (loop (not phase))] + [else + (define c1x x) + (define c1y (+ y (shift stack))) + (define c2x (+ c1x (shift stack))) + (define c2y (+ c1y (shift stack))) + (set! x (+ c2x (shift stack))) + (set! y (+ c2y (if (= (stack-length stack) 1) (shift stack) 0))) + (path-bezierCurveTo path c1x c1y c2x c2y x y) + (loop (not phase))])))] + [(12) + (println "warning: check truthiness") + (set! op (read-byte stream)) + (case= op + [(3) ;; and + (push stack (if (and (pop stack) (pop stack)) 1 0))] + [(4) ;; or + (push stack (if (or (pop stack) (pop stack)) 1 0))] + [(5) ;; not + (push stack (if (pop stack) 0 1))] + [(9) ;; abs + (push stack (abs (pop stack)))] + [(10) ;; add + (push stack (+ (pop stack) (pop stack)))] + [(11) ;; sub + (push stack (- (pop stack) (pop stack)))] + [(12) ;; div + (push stack (/ (pop stack) (pop stack) 1.0))] + [(14) ;; neg + (push stack (- (pop stack)))] + [(15) ;; eq + (push stack (if (- (pop stack) (pop stack)) 1 0))] + [(18) ;; drop + (pop stack)] + [(20) ;; put + (define val (pop stack)) + (define idx (pop stack)) + (set! trans (list-set trans idx val))] + [(21) ;; get + (push stack (or (list-ref trans (pop stack)) 0))] + [(22) ;; ifelse + (define s1 (pop stack)) + (define s2 (pop stack)) + (define v1 (pop stack)) + (define v2 (pop stack)) + (push stack (if (<= v1 v2) s1 s2))] + [(23) ;; random + (push stack (random))] + [(24) ;; mul + (push stack (* (pop stack) (pop stack)))] + [(26) ;; sqrt + (push stack (sqrt (pop stack)))] + [(26) ;; dup + (define a (pop stack)) + (push stack a a)] + [(28) ;; exch + (define a (pop stack)) + (define b (pop stack)) + (push stack b a)] + [(29) ;; index + (define idx + (min (max 0 (pop stack)) (- (stack-length stack) 1))) + (push stack (list-ref stack idx))] + [(30) ;; roll + (define n (pop stack)) + (define j (pop stack)) + (cond + [(>= j 0) + (let loop ([j j]) + (when (positive? j) + (define t (list-ref stack (- n 1))) + (for [(i (in-range (- n 2) (sub1 0) -1))] + (set! stack (list-set stack (+ i 1) (list-ref stack i)))) + (set! stack (list-set stack 0 t)) + (loop (sub1 j))))] + [else + (let loop ([j j]) + (when (negative? j) + (define t (list-ref stack 0)) + (for ([i (in-range (add1 n))]) + (set! stack (list-set stack i (list-ref stack (+ i 1))))) + (set! stack (list-set stack (- n 1) t)) + (loop (add1 j))))])] + [(34) ;; hflex + (define c1x (+ x (shift stack))) + (define c1y y) + (define c2x (+ c1x (shift stack))) + (define c2y (+ c1y (shift stack))) + (define c3x (+ c2x (shift stack))) + (define c3y c2y) + (define c4x (+ c3x (shift stack))) + (define c4y c3y) + (define c5x (+ c4x (shift stack))) + (define c5y c4y) + (define c6x (+ c5x (shift stack))) + (define c6y c5y) + (set! x c6x) + (set! y c6y) + (path-bezierCurveTo path c1x c1y c2x c2y c3x c3y) + (path-bezierCurveTo path c4x c4y c5x c5y c6x c6y)] + [(35) ;; flex + (define pts null) + (for ([i (in-range (add1 5))]) + (set! x (+ x (shift stack))) + (set! y (+ y (shift stack))) + (push pts x y)) + (apply path-bezierCurveTo path (take pts 6)) + (apply path-bezierCurveTo path (drop pts 6)) + (shift stack)] ;; fd + [(36) ;; hflex1 + (define c1x (+ x (shift stack))) + (define c1y (+ y (shift stack))) + (define c2x (+ c1x (shift stack))) + (define c2y (+ c1y (shift stack))) + (define c3x (+ c2x (shift stack))) + (define c3y c2y) + (define c4x (+ c3x (shift stack))) + (define c4y c3y) + (define c5x (+ c4x (shift stack))) + (define c5y (+ c4y (shift stack))) + (define c6x (+ c5x (shift stack))) + (define c6y c5y) + (set! x c6x) + (set! y c6y) + (path-bezierCurveTo path c1x c1y c2x c2y c3x c3y) + (path-bezierCurveTo path c4x c4y c5x c5y c6x c6y)] + [(37) ;; flex1 + (define startx x) + (define starty y) + (define pts null) + (for ([i (in-range 0 (add1 4))]) + (set! x (+ x (shift stack))) + (set! y (+ y (shift stack))) + (push pts x y)) + (cond + [(> (abs (- x startx)) (abs (- y starty))) ;; horzontal + (set! x (shift stack)) + (set! y starty)] + [else + (set! x startx) + (set! y (shift stack))]) + (push pts x y) + (apply path-bezierCurveTo path (take pts 6)) + (apply path-bezierCurveTo path (drop pts 6))] + [else (error (format "unknown op: 12 ~a" op))])] + [else (error (format "unknown op: ~a" op))])] + [else + + (push stack (cond + [(< op 247) (- op 139)] + [(< op 251) (+ (* (- op 247) 256) (read-byte stream) 108)] + [(< op 255) (- (* (- 251 op) 256) (read-byte stream) 108)] + [else (/ (decode int32be stream) 65536.0)]))])))) + + (when open (path-closePath path)) + (set-cff-glyph-_usedSubrs! this used-subrs) + (set-cff-glyph-_usedGsubrs! this used-gsubrs) + (set-cff-glyph-path! this path) + path) diff --git a/fontland/fontland/db.rkt b/fontland/fontland/db.rkt new file mode 100644 index 00000000..322f7ade --- /dev/null +++ b/fontland/fontland/db.rkt @@ -0,0 +1,36 @@ +#lang debug racket +(require sugar/cache racket/match + db racket/runtime-path) +(provide (all-defined-out)) + +(define-runtime-path db-file "fontland.sqlite") +(define current-query-debug (make-parameter #f)) +(define current-dbc (make-parameter (sqlite3-connect #:database db-file #:mode 'create))) + +(define-logger db) + +(define (log-query q) (when (current-query-debug) (log-db-info q))) + +(define-syntax-rule (query-exec-logging q arg ...) + (begin (log-query q) (query-exec (current-dbc) q arg ...))) + +(define-syntax-rule (query-rows-logging q arg ...) + (begin (log-query q) (query-rows (current-dbc) q arg ...))) + +(define (add-record! rec) + (query-exec-logging "insert or replace into layouts (crc, layout) values ($1, $2)" (car rec) (cdr rec))) + +(define (get-layout-from-db which) + (match (query-rows-logging "select layout from layouts where crc==$1" which) + [(list (vector val)) val] + [_ #false])) + +(define (init-db) + (query-exec-logging "create table if not exists layouts (crc INTEGER UNIQUE, layout BLOB)") + (query-exec-logging "create index if not exists layout_crcs ON layouts (crc); +")) + +(module+ main + (init-db) + (add-record! (cons 42 (bytes 0 1 0 2 3 4))) + (get-layout-from-db 42)) \ No newline at end of file diff --git a/fontland/fontland/deque.rkt b/fontland/fontland/deque.rkt new file mode 100644 index 00000000..14315c20 --- /dev/null +++ b/fontland/fontland/deque.rkt @@ -0,0 +1,122 @@ +#lang debug racket/base +(require racket/struct racket/match) +(provide (all-defined-out)) + +(struct deque (start length) #:mutable + #:methods gen:custom-write + [(define write-proc + (make-constructor-style-printer + (λ (d) 'deque) + (λ (d) (deque->list d))))]) +(struct deque-item (val prev next) #:mutable) + +(define (before! di1 di2) + (set-deque-item-next! di1 di2) + (set-deque-item-prev! di2 di1)) + +(define (insert-before! new-di di) + ((deque-item-prev di) . before! . new-di) + (new-di . before! . di)) + +(define (remove! di) + ((deque-item-prev di) . before! . (deque-item-next di))) + +(define (push-end! d . vals) + (apply push-start! d #:end #true vals)) + +(define (push-start! d #:end [end? #f] . vals) + (define-values (val-count first-di) + (for/fold ([count 0] + [first-di #f]) + ([val (in-list vals)]) + (define di (deque-item val #f #f)) + (match (deque-start d) + [#false (set-deque-start! d di) + (before! di di)] + [start (di . insert-before! . start)]) + (values (add1 count) (or first-di di)))) + (unless (zero? val-count) + (unless end? + (set-deque-start! d first-di)) + (set-deque-length! d (+ val-count (deque-length d))))) + +(define (pop-start! d #:end [end? #f]) + (unless (zero? (deque-length d)) + (define popdi ((if end? deque-item-prev values) (deque-start d))) + (begin0 + (deque-item-val popdi) + (set-deque-start! d (and (> (deque-length d) 1) (deque-item-next popdi))) + (remove! popdi) + (set-deque-length! d (sub1 (deque-length d)))))) + +(define (pop-end! d) + (pop-start! d #:end #true)) + +(define (make-deque . vals) + (define d (deque #f 0)) + (apply push-end! d vals) + d) + +(define (deque-ref d idx) + (unless (< idx (deque-length d)) + (error 'deque-idx-too-large)) + (for/fold ([di (deque-start d)] + #:result (deque-item-val di)) + ([i (in-range idx)]) + (deque-item-next di))) + +(define (deque-rotate! d [count 0]) + (unless (or (zero? count) (< (deque-length d) 2)) + (cond + [(< (abs count) (deque-length d)) + (define opp-count ((if (positive? count) - +) (- (deque-length d) (abs count)))) + (define new-count (if (< (abs opp-count) (abs count)) opp-count count)) + (define dir (if (positive? new-count) deque-item-next deque-item-prev)) + (for/fold ([di (deque-start d)] + #:result (set-deque-start! d di)) + ([i (in-range (abs new-count))]) + (dir di))] + [else (deque-rotate! d (modulo count ((if (positive? count) + -) (deque-length d))))]))) + +(define (deque->list d) + (for/fold ([vals null] + [di (deque-start d)] + #:result (reverse vals)) + ([i (in-range (deque-length d))]) + (values (cons (deque-item-val di) vals) (deque-item-next di)))) + +(define (list->deque xs) + (apply make-deque xs)) + +(module+ test + (require rackunit) + (define d (make-deque 42)) + (check-equal? (deque-length d) 1) + (push-end! d 43 44 45) + (check-equal? (deque-length d) 4) + (check-equal? (deque-ref d 0) 42) + (check-equal? (deque-ref d 1) 43) + (check-equal? (deque-ref d 2) 44) + (check-equal? (deque-ref d 3) 45) + (push-start! d 39 40 41) + (check-equal? (deque-length d) 7) + (check-equal? (deque-ref d 0) 39) + (check-equal? (deque-ref d 1) 40) + (check-equal? (deque-ref d 2) 41) + (check-equal? (deque-ref d 3) 42) + (check-equal? (pop-start! d) 39) + (check-equal? (pop-start! d) 40) + (check-equal? (pop-end! d) 45) + (check-equal? (pop-end! d) 44) + (check-equal? (deque-length d) 3) + (check-equal? (deque->list d) '(41 42 43)) + (deque-rotate! d 1) + (check-equal? (deque->list d) '(42 43 41)) + (deque-rotate! d -1) + (check-equal? (deque->list d) '(41 42 43)) + (deque-rotate! d (deque-length d)) + (check-equal? (deque->list d) '(41 42 43)) + (let ([d (make-deque 90)]) + (pop-start! d) + (push-end! d -1) + (check-equal? (deque->list d) (list -1)))) diff --git a/fontland/fontland/directory.rkt b/fontland/fontland/directory.rkt new file mode 100644 index 00000000..c961ee73 --- /dev/null +++ b/fontland/fontland/directory.rkt @@ -0,0 +1,79 @@ +#lang racket/base +(require xenomorph + "tables.rkt" + racket/dict + racket/class + racket/match + sugar/unstable/dict + racket/string) + +(provide (all-defined-out)) + +#| +https://github.com/mbutterick/fontkit/blob/master/src/tables/directory.js +|# + +(define table-entry (x:struct + 'tag (x:symbol #:length 4) + 'checkSum uint32be + 'offset (x:pointer #:type uint32be + #:dest-type 'void + #:relative-to 'global) + 'length uint32be)) + +;; for stupid tags like 'cvt ' +(define (symbol-replace sym this that) + (string->symbol (string-replace (if (string? sym) sym (symbol->string sym)) this that))) +(define (escape-tag tag) (symbol-replace tag " " "_")) +(define (unescape-tag tag) (symbol-replace tag "_" " ")) + +(define (directory-post-decode this-res) + (define new-tables-val (mhash)) + (for ([table (in-list (hash-ref this-res 'tables))]) + (hash-set! new-tables-val (escape-tag (hash-ref table 'tag)) table)) + (dict-set! this-res 'tables new-tables-val) + this-res) + +(define (directory-pre-encode this-val) + (define tables (for/list ([tag-table-pair (in-list (hash-ref this-val 'tables))]) + (match-define (cons tag table) tag-table-pair) + (define table-codec (hash-ref table-codecs tag)) + (mhash 'tag (unescape-tag tag) + 'checkSum 0 + 'offset (x:void-pointer table-codec table) + 'length (send table-codec x:size table)))) + (define numTables (length tables)) + ;; patch from https://github.com/foliojs/fontkit/pull/178 + (define max-exponent-for-2 (floor (log numTables 2))) + (define searchRange (* (expt 2 max-exponent-for-2) 16)) + (hash-set*! this-val + 'tag 'true + 'numTables numTables + 'tables tables + 'searchRange searchRange + 'entrySelector max-exponent-for-2 + 'rangeShift (- (* numTables 16) searchRange)) + this-val) + +(define directory (x:struct #:pre-encode directory-pre-encode + #:post-decode directory-post-decode + 'tag (x:symbol #:length 4) + 'numTables uint16be + 'searchRange uint16be + 'entrySelector uint16be + 'rangeShift uint16be + 'tables (x:array #:type table-entry #:length (λ (p) (hash-ref p 'numTables))))) + +(define (directory-decode ip [options (mhash)]) + (decode directory ip)) + +(define (file-directory-decode ps) + (directory-decode (open-input-file ps))) + +#;(module+ test + (require rackunit "helper.rkt" racket/serialize racket/file racket/pretty) + (define ip (open-input-file fira-otf-path)) + (define dir (serialize (directory-decode ip))) + (pretty-write dir) + (with-output-to-file "assets/fira-otf-directory.rktd" + (λ () (pretty-write dir)) #:exists 'replace)) \ No newline at end of file diff --git a/fontland/fontland/font-path.rkt b/fontland/fontland/font-path.rkt new file mode 100644 index 00000000..4447ef42 --- /dev/null +++ b/fontland/fontland/font-path.rkt @@ -0,0 +1,66 @@ +#lang debug racket/base +(require racket/promise + racket/string + "unsafe/fontconfig.rkt") +(provide (all-defined-out)) + +(define fontsets-promise + (delay + (define font-dirs + (case (system-type 'os) + [(macosx) + ;; https://support.apple.com/en-us/HT201722 + (list "/System/Library/Fonts" + "/Library/Fonts" + (expand-user-path "~/Library/Fonts"))] + ;; https://wiki.ubuntu.com/Fonts#Manually + [(unix) (list "/usr/share/fonts" + "/usr/local/share/fonts" + (expand-user-path "~/.fonts"))] + [else ;; windows + ;; https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows + ;; on my windows 10 VM, the 'sys-dir is C:\\Windows\system32 + ;; though I'm suspicious that it's always like this + (list (build-path (find-system-path 'sys-dir) 'up "fonts"))])) + (define (path->fontset path-or-path-string) + (define bytepath (path->bytes (if (string? path-or-path-string) + (string->path path-or-path-string) + path-or-path-string))) + ((cond + [(fc-file-is-dir bytepath) fc-dir-scan] + [else fc-file-scan]) bytepath)) + (map path->fontset font-dirs))) + +(define (probably-successful-match? family-name result) + ;; fontconfig does its best to find a match using fuzzy logic + ;; so there's no official failure condition, it seems. + ;; this test checks if the first letters (up to 6) of the family name + ;; appear somewhere in the font filename. + (define-values (dir name _) (split-path result)) + (define name-string (path->string name)) + (regexp-match (string-downcase (substring family-name 0 (min 6 (string-length family-name)))) (string-downcase name-string))) + +;; this function follows c sample +;; https://gist.github.com/CallumDev/7c66b3f9cf7a876ef75f + +(define (family->path family-name #:bold [bold? #f] #:italic [italic? #f]) + ;; create a configuration & invoke it + (fc-config-set-current (fc-config-create)) + (define fontsets (force fontsets-promise)) + (cond + [(ormap values fontsets) + ;; query pattern syntax + ;; https://www.freedesktop.org/software/fontconfig/fontconfig-user.html#AEN36 + (define query-pattern + (fc-name-parse (string->bytes/utf-8 (format "~a:weight=~a:slant=~a" family-name (if bold? 200 80) (if italic? 100 0))))) + (fc-config-substitute query-pattern 'FcMatchPattern) + ;; https://www.freedesktop.org/software/fontconfig/fontconfig-devel/fcdefaultsubstitute.html + ;; Supplies default values for underspecified font patterns + (fc-default-substitute query-pattern) + (define result-pattern (fc-font-set-match fontsets query-pattern)) + (define result + (and result-pattern (bytes->path (fc-pattern-get-string result-pattern #"file" 0)))) + (cond + [(and result (probably-successful-match? family-name result)) result] + [else #false])] + [else #false])) diff --git a/fontland/fontland/font.rkt b/fontland/fontland/font.rkt new file mode 100644 index 00000000..d85537f5 --- /dev/null +++ b/fontland/fontland/font.rkt @@ -0,0 +1,220 @@ +#lang debug racket/base +(require "helper.rkt" + "unsafe/freetype.rkt" + "glyph.rkt" + "bbox.rkt" + "glyphrun.rkt" + "directory.rkt" + "woff-directory.rkt" + "woff2-directory.rkt" + "struct.rkt" + "table-stream.rkt" + "font-path.rkt" + xenomorph + racket/match + racket/list + racket/string + sugar/unstable/dict + "unsafe/harfbuzz.rkt" + "glyph-position.rkt" + sugar/list + racket/promise) +(provide (all-defined-out)) + + + +(define ft-library (delay (FT_Init_FreeType))) + +#| +approximates +https://github.com/mbutterick/fontkit/blob/master/src/TTFFont.js +|# + +(struct probe-fail exn ()) + +(define (+ttf-font port + #:directory [directory-class directory] + #:probe [probe-vals (list #"true" #"OTTO" (bytes 0 1 0 0))] + #:constructor [structor ttf-font]) + + (unless (input-port? port) + (raise-argument-error '+ttf-font "input port" port)) + (unless (member (peek-bytes 4 0 port) probe-vals) + (raise (probe-fail "fail" (current-continuation-marks)))) + + (define decoded-tables (mhash)) + (define src (path->string (object-name port))) + (define directory (delay (decode directory-class port #:parent (mhash x:start-offset-key 0)))) + (define ft-face (delay (and src (FT_New_Face (force ft-library) src)))) + (define hb-font (delay (and src (hb_ft_font_create (force ft-face))))) + (define hb-buf (delay (hb_buffer_create))) + (define crc (equal-hash-code port)) + (define get-head-table-proc #f) + + (define font + (structor port decoded-tables src directory ft-face hb-font hb-buf crc get-head-table-proc)) + ;; needed for `loca` table decoding cross-reference + (set-ttf-font-get-head-table-proc! font (delay (get-head-table font))) + font) + +(define (font-postscript-name font) (FT_Get_Postscript_Name (ft-face font))) +(define (font-units-per-em font) (hash-ref (get-head-table font) 'unitsPerEm)) +(define (font-ascent font) (hash-ref (get-hhea-table font) 'ascent)) +(define (font-descent font) (hash-ref (get-hhea-table font) 'descent)) +(define (font-linegap font) (hash-ref (get-hhea-table font) 'lineGap)) +(define (font-underline-position font) (hash-ref (get-post-table font) 'underlinePosition)) +(define (font-underline-thickness font) (hash-ref (get-post-table font) 'underlineThickness)) +(define (font-italic-angle font) (hash-ref (get-post-table font) 'italicAngle)) + +(define (font-cap-height font) + (if (has-table? font #"OS/2") + (hash-ref (get-OS/2-table font) 'capHeight) + (font-ascent font))) + +(define (font-x-height font) + (if (has-table? font #"OS/2") + (hash-ref (get-OS/2-table font) 'xHeight) + 0)) + +(define (font-bbox font) + (define head-table (get-head-table font)) + (+bbox (hash-ref head-table 'xMin) (hash-ref head-table 'yMin) + (hash-ref head-table 'xMax) (hash-ref head-table 'yMax))) + + +#| +approximates +https://github.com/mbutterick/fontkit/blob/master/src/WOFFFont.js +|# + +(define (+woff-font port) + (+ttf-font port + #:directory woff-directory + #:probe (list #"wOFF") + #:constructor woff-font)) + + +#| +approximates +https://github.com/mbutterick/fontkit/blob/master/src/WOFF2Font.js +|# + +(define (+woff2-font port) + (+ttf-font port + #:directory woff2-directory + #:probe (list #"wOF2") + #:constructor woff-font)) + +;; 181228: disk-based caching (either with sqlite or `with-cache`) is a loser +;; reads & writes aren't worth it vs. recomputing +;; (though this is good news, as it avoids massive disk caches hanging around) +;; ram cache in pitfall suffices + +(define (layout font str + #:features [features null] + #:script [script #f] + #:language [lang #f] + #:direction [direction #f]) + (match (for/list ([c (in-string str)]) (char->integer c)) + [(? null?) (glyphrun (vector) (vector))] + [codepoints + (define buf (hb-buf font)) + (hb_buffer_reset buf) + (when script + (hb_buffer_set_script buf script)) + (when lang + (hb_buffer_set_language buf (hb_language_from_string lang))) + (when direction + (hb_buffer_set_direction buf direction)) + (hb_buffer_add_codepoints buf codepoints) + (hb_shape (hb-font font) buf (map (λ (fpr) (tag->hb-feature (car fpr) (cdr fpr))) features)) + (define gis (hb_buffer_get_glyph_infos buf)) + (define hb-gids (map hb_glyph_info_t-codepoint gis)) + ;; `remove-duplicates` in case we get a funny codepoint that expands into multiple glyphs (rare but possible) + (define hb-clusters (break-at codepoints (remove-duplicates (map hb_glyph_info_t-cluster gis) eq?))) + (define hb-positions (map hb_glyph_position_t->list (hb_buffer_get_glyph_positions buf))) + (define glyphs (for/vector ([gidx (in-list hb-gids)] + [cluster (in-list hb-clusters)]) + (get-glyph font gidx cluster))) + (define positions (for/vector ([posn (in-list hb-positions)]) + (apply +glyph-position posn))) + (glyphrun glyphs positions)])) + + +#| +approximates +https://github.com/mbutterick/fontkit/blob/master/src/index.js +|# + +#| +approximates +https://github.com/mbutterick/fontkit/blob/master/src/base.js +|# + + +#;(define (query-fontconfig fam [bold #f] [italic #f]) + #| +https://www.freedesktop.org/software/fontconfig/fontconfig-user.html +Fontconfig provides a textual representation for patterns that the library can both accept and generate. The representation is in three parts, first a list of family names, second a list of point sizes and finally a list of additional properties: + -:=:=... +|# + + (define pat-str (string-join (filter values + (list fam + (and bold "bold") + (and italic "italic"))) ":")) + (define fp (fc-name-parse (string->bytes/utf-8 pat-str))) + (fc-config-substitute (fc-config-get-current) fp 'FcMatchPattern) + (fc-default-substitute fp) + (define-values (res pat) (fc-font-match #f fp)) + (for/hasheq ([str (cdr (string-split (bytes->string/utf-8 (fc-name-unparse pat)) ":"))]) + (let loop ([kv (string-split str "=")]) + (match kv + [(list k) (loop (list k "True"))] + [(list k v) (values (string->symbol k) + (match v + ["True" #t] + ["False" #f] + [(? string->number) (string->number v)] + [val val]))])))) + +(define (open-font str-or-path #:bold [bold #f] #:italic [italic #f]) + ;; rather than use a `probe` function, + ;; just try making a font with each format and see what happens + (define str (if (path? str-or-path) (path->string str-or-path) str-or-path)) + (or + ;; wrapping these in thunks so that they are not evaluated until the iteration demands it + (for*/or ([path-string-thunk (in-list (list (λ () str) + (λ () (family->path str #:bold bold #:italic italic))))] + [path-string (in-value (path-string-thunk))] + #:when (and path-string (file-exists? path-string)) + [port (in-value (open-input-file path-string))] + [font-constructor (in-list (list +ttf-font +woff-font))]) + (with-handlers ([probe-fail? (λ (exn) #f)]) + (font-constructor port))) + (raise-argument-error 'open-font "valid font" str-or-path))) + +(module+ test + (require rackunit racket/struct racket/vector) + (define charter (open-font charter-path)) + (define charter-woff (open-font charter-woff-path)) + (define fira (open-font (path->string fira-path))) + (define otf (open-font (path->string fira-otf-path))) + (for ([charter (list charter charter-woff)]) + (check-equal? (font-postscript-name charter) "Charter") + (check-equal? (font-units-per-em charter) 1000) + (check-equal? (font-ascent charter) 980) + (check-equal? (font-descent charter) -238) + (check-equal? (font-linegap charter) 0) + (check-equal? (font-underline-thickness charter) 58) + (check-equal? (font-italic-angle charter) 0) + (check-equal? (font-cap-height charter) 671) + (check-equal? (font-x-height charter) 481) + (check-equal? (bbox->list (font-bbox charter)) '(-161 -236 1193 963)) + (check-equal? (glyph-position-x-advance (vector-ref (glyphrun-positions (layout charter "f")) 0)) 321) + (check-true (has-table? charter #"cmap")) + (check-exn exn:fail:contract? (λ () (get-table charter 'nonexistent-table-tag)))) + (check-true + (let ([gr (layout fira "Rifle")]) + (and (equal? (vector-map glyph-id (glyphrun-glyphs gr)) '#(227 480 732 412)) + (equal? (vector-map struct->list (glyphrun-positions gr)) '#((601 0 0 0 0) (279 0 0 0 0) (580 0 0 0 0) (547 0 0 0 0))))))) diff --git a/fontland/fontland/glyph-position.rkt b/fontland/fontland/glyph-position.rkt new file mode 100644 index 00000000..cd7b487d --- /dev/null +++ b/fontland/fontland/glyph-position.rkt @@ -0,0 +1,26 @@ +#lang racket/base +(provide (all-defined-out)) + +;; Represents positioning information for a glyph in a GlyphRun. +;; x-advance = The amount to move the virtual pen in the X direction after rendering this glyph. +;; y-advance = The amount to move the virtual pen in the Y direction after rendering this glyph. +;; x-offset = The offset from the pen position in the X direction at which to render this glyph. +;; y-offset = The offset from the pen position in the Y direction at which to render this glyph. +(struct glyph-position (x-advance y-advance x-offset y-offset advance-width) #:transparent #:mutable) + +(define (+glyph-position [x-advance 0] [y-advance 0] [x-offset 0] [y-offset 0] [advance-width 0]) + (glyph-position x-advance y-advance x-offset y-offset advance-width)) + +(define (scale-glyph-position! gp factor) + (set-glyph-position-x-advance! gp (* factor (glyph-position-x-advance gp))) + (set-glyph-position-y-advance! gp (* factor (glyph-position-y-advance gp))) + (set-glyph-position-x-offset! gp (* factor (glyph-position-x-offset gp))) + (set-glyph-position-y-offset! gp (* factor (glyph-position-y-offset gp))) + (set-glyph-position-advance-width! gp (* factor (glyph-position-advance-width gp))) + gp) + +(module+ test + (require rackunit) + (define gp (+glyph-position 1 2 3 4)) + (check-true (glyph-position? gp)) + (check-equal? (scale-glyph-position! gp 2) (+glyph-position 2 4 6 8 0))) diff --git a/fontland/fontland/glyph.rkt b/fontland/fontland/glyph.rkt new file mode 100644 index 00000000..e68dc2b2 --- /dev/null +++ b/fontland/fontland/glyph.rkt @@ -0,0 +1,67 @@ +#lang debug racket/base +(require sugar/unstable/dict + "unsafe/freetype.rkt" + "table-stream.rkt" + "struct.rkt" + "helper.rkt") +(provide (all-defined-out)) + +#| +approximates +https://github.com/mbutterick/fontkit/blob/master/src/glyph/Glyph.js +|# + +; Glyph objects represent a glyph in the font. They have various properties for accessing metrics and +; the actual vector path the glyph represents, and methods for rendering the glyph to a graphics context. +; +; You do not create glyph objects directly. They are created by various methods on the font object. +; There are several subclasses of the base Glyph class internally that may be returned depending +; on the font format, but they all inherit from this class. + +(define (is-mark? codepoint) + ;; mark classes = Mn Me Mc + (regexp-match #px"\\p{Mn}|\\p{Me}|\\p{Mc}" (string (integer->char codepoint)))) + +(define (+glyph id codepoints font + [is-mark? (andmap is-mark? codepoints)] + [is-ligature? (> (length codepoints) 1)] + [metrics #f] + #:constructor [constructor glyph]) + (constructor id codepoints font is-mark? is-ligature? metrics)) + +(define (glyph-advance-width g) + (hash-ref (get-glyph-metrics g) 'advanceWidth)) + +(define (get-glyph-metrics g) + (unless (glyph-metrics g) + (define face (ft-face (glyph-font g))) + (FT_Load_Glyph face (glyph-id g)) + (define glyph (FT_FaceRec-glyph face)) + (define ft-glyph-metrics (FT_GlyphSlotRec-metrics glyph)) + (set-glyph-metrics! g (mhash)) + ;; todo: get vertical metrics + (hash-set*! (glyph-metrics g) + 'advanceWidth (FT_Glyph_Metrics-horiAdvance ft-glyph-metrics) + 'leftBearing (FT_Glyph_Metrics-horiBearingX ft-glyph-metrics) + 'advanceHeight 'unfinished + 'topBearing 'unfinished)) + (glyph-metrics g)) + +(define (+ttf-glyph . args) + (apply +glyph #:constructor ttf-glyph args)) + +(define (+cff-glyph . args) + (apply +glyph #:constructor make-cff-glyph args)) + +#| +approximates +https://github.com/mbutterick/fontkit/blob/master/src/glyph/TTFFont.js +|# + +;; Returns a glyph object for the given glyph id. +;; You can pass the array of code points this glyph represents for +;; your use later, and it will be stored in the glyph object. +(define (get-glyph font gid [codepoints null]) + ((if (has-table? font #"CFF_") + +cff-glyph + +ttf-glyph) gid codepoints font)) \ No newline at end of file diff --git a/fontland/fontland/glyphrun.rkt b/fontland/fontland/glyphrun.rkt new file mode 100644 index 00000000..fc1ad291 --- /dev/null +++ b/fontland/fontland/glyphrun.rkt @@ -0,0 +1,37 @@ +#lang racket/base +(require "glyph-position.rkt") +(provide (all-defined-out)) + +#| +approximates +https://github.com/mbutterick/fontkit/blob/master/src/layout/GlyphRun.js +|# + +;; Represents a run of Glyph and GlyphPosition objects. +;; Returned by the font layout method. +; An array of Glyph objects in the run +; An array of GlyphPosition objects for each glyph in the run +(struct glyphrun (glyphs positions) #:transparent) + +(define (+glyphrun [glyphs null] [positions null]) + (glyphrun glyphs positions)) + +(define (glyphrun-advance-width gr) + (for/sum ([pos (in-list (glyphrun-positions gr))]) + (glyph-position-x-advance pos))) + +(define (append-glyphruns . grs) + (for/fold ([glyphss null] + [positionss null] + #:result (glyphrun + (apply append (reverse glyphss)) + (apply append (reverse positionss)))) + ([gr (in-list grs)]) + (values (cons (glyphrun-glyphs gr) glyphss) + (cons (glyphrun-positions gr) positionss)))) + +(module+ test + (require rackunit) + (define gr (+glyphrun)) + (check-true (glyphrun? gr)) + (check-equal? (append-glyphruns gr gr) gr)) diff --git a/fontland/fontland/helper.rkt b/fontland/fontland/helper.rkt new file mode 100644 index 00000000..ee52d6e1 --- /dev/null +++ b/fontland/fontland/helper.rkt @@ -0,0 +1,17 @@ +#lang racket +(require racket/runtime-path) +(provide (all-defined-out)) + +(define-syntax-rule (r+p ID ...) + (begin (require ID ...) (provide (all-from-out ID ...)))) + +(define index? (λ (x) (and (number? x) (integer? x) (not (negative? x))))) + +(define-runtime-path charter-path "assets/charter.ttf") +(define-runtime-path charter-woff-path "assets/charter.woff") +(define-runtime-path charter-italic-path "assets/charter-italic.ttf") +(define-runtime-path fira-path "assets/fira.ttf") +(define-runtime-path fira-otf-path "assets/fira.otf") +(define-runtime-path charter-directory-path "assets/charter-directory.rktd") +(define-runtime-path charter-italic-directory-path "assets/charter-italic-directory.rktd") +(define-runtime-path fira-otf-directory-path "assets/fira-otf-directory.rktd") \ No newline at end of file diff --git a/fontland/fontland/main.rkt b/fontland/fontland/main.rkt new file mode 100644 index 00000000..0306534b --- /dev/null +++ b/fontland/fontland/main.rkt @@ -0,0 +1,16 @@ +#lang racket/base + +(define-syntax-rule (r+p ID ...) + (begin (require ID ...) (provide (all-from-out ID ...)))) + +(r+p "font.rkt" + "glyph-position.rkt" + "subset.rkt" + "bbox.rkt" + "tables.rkt" + "glyphrun.rkt" + "glyph.rkt" + "table-stream.rkt" + "subset.rkt" + "struct.rkt" + "zlib.rkt") \ No newline at end of file diff --git a/fontland/fontland/path.rkt b/fontland/fontland/path.rkt new file mode 100644 index 00000000..97c28c44 --- /dev/null +++ b/fontland/fontland/path.rkt @@ -0,0 +1,59 @@ +#lang debug racket +(require sugar/unstable/dict + (for-syntax racket/syntax)) +(provide (all-defined-out)) + +#| +approximates +https://github.com/mbutterick/fontkit/blob/master/src/glyph/Path.js +|# + +#| +/** + * Path objects are returned by glyphs and represent the actual + * vector outlines for each glyph in the font. Paths can be converted + * to SVG path data strings, or to functions that can be applied to + * render the path to a graphics context. + */ +|# + +(struct Path$ (commands _bbox _cbox) #:transparent #:mutable) + +(define (Path [commands null] [_bbox #false] [_cbox #false]) + (Path$ commands _bbox _cbox)) + +(define SVG_COMMANDS (hasheq 'moveTo "M" + 'lineTo "L" + 'quadraticCurveTo "Q" + 'bezierCurveTo "C" + 'closePath "Z")) + +(define (toSVG this) + (define cmds (for/list ([c (in-list (Path$-commands this))]) + (define args (for/list ([arg (in-list (dict-ref c 'args))]) + (round (/ (* arg 100) 100)))) + (format "~a~a" (hash-ref SVG_COMMANDS (dict-ref c 'command)) + (string-join (map ~a args) " ")))) + (string-join cmds "")) + +(define-syntax (define-command stx) + (syntax-case stx () + [(_ COMMAND) + (with-syntax [(ID (format-id #'COMMAND (format "path-~a" (syntax-e #'COMMAND))))] + #'(define (ID this . args) + (set-Path$-_bbox! this #false) + (set-Path$-_cbox! this #false) + (set-Path$-commands! this + (append + (Path$-commands this) + (list + (dictify + 'command 'COMMAND + 'args args)))) + this))])) + +(define-command moveTo) +(define-command lineTo) +(define-command quadraticCurveTo) +(define-command bezierCurveTo) +(define-command closePath) \ No newline at end of file diff --git a/fontland/fontland/script.rkt b/fontland/fontland/script.rkt new file mode 100644 index 00000000..3c938c28 --- /dev/null +++ b/fontland/fontland/script.rkt @@ -0,0 +1,186 @@ +#lang racket/base +(require sugar/unstable/dict + sugar/unstable/contract + sugar/unstable/stub + racket/contract) + +(provide (all-defined-out)) + +#| +https://github.com/mbutterick/fontkit/blob/master/src/layout/Script.js +|# + +;; This maps the Unicode Script property to an OpenType script tag +;; Data from http://www.microsoft.com/typography/otspec/scripttags.htm +;; and http://www.unicode.org/Public/UNIDATA/PropertyValueAliases.txt. + +(define UNICODE_SCRIPTS + (apply mhash + '(Caucasian_Albanian aghb + Arabic arab + Imperial_Aramaic armi + Armenian armn + Avestan avst + Balinese bali + Bamum bamu + Bassa_Vah bass + Batak batk + Bengali '(bng2 beng) + Bopomofo bopo + Brahmi brah + Braille brai + Buginese bugi + Buhid buhd + Chakma cakm + Canadian_Aboriginal cans + Carian cari + Cham cham + Cherokee cher + Coptic copt + Cypriot cprt + Cyrillic cyrl + Devanagari '(dev2 deva) + Deseret dsrt + Duployan dupl + Egyptian_Hieroglyphs egyp + Elbasan elba + Ethiopic ethi + Georgian geor + Glagolitic glag + Gothic goth + Grantha gran + Greek grek + Gujarati '(gjr2 gujr) + Gurmukhi '(gur2 guru) + Hangul hang + Han hani + Hanunoo hano + Hebrew hebr + Hiragana hira + Pahawh_Hmong hmng + Katakana_Or_Hiragana hrkt + Old_Italic ital + Javanese java + Kayah_Li kali + Katakana kana + Kharoshthi khar + Khmer khmr + Khojki khoj + Kannada '(knd2 knda) + Kaithi kthi + Tai_Tham lana + Lao lao + Latin latn + Lepcha lepc + Limbu limb + Linear_A lina + Linear_B linb + Lisu lisu + Lycian lyci + Lydian lydi + Mahajani mahj + Mandaic mand + Manichaean mani + Mende_Kikakui mend + Meroitic_Cursive merc + Meroitic_Hieroglyphs mero + Malayalam '(mlm2 mlym) + Modi modi + Mongolian mong + Mro mroo + Meetei_Mayek mtei + Myanmar '(mym2 mymr) + Old_North_Arabian narb + Nabataean nbat + Nko nko + Ogham ogam + Ol_Chiki olck + Old_Turkic orkh + Oriya orya + Osmanya osma + Palmyrene palm + Pau_Cin_Hau pauc + Old_Permic perm + Phags_Pa phag + Inscriptional_Pahlavi phli + Psalter_Pahlavi phlp + Phoenician phnx + Miao plrd + Inscriptional_Parthian prti + Rejang rjng + Runic runr + Samaritan samr + Old_South_Arabian sarb + Saurashtra saur + Shavian shaw + Sharada shrd + Siddham sidd + Khudawadi sind + Sinhala sinh + Sora_Sompeng sora + Sundanese sund + Syloti_Nagri sylo + Syriac syrc + Tagbanwa tagb + Takri takr + Tai_Le tale + New_Tai_Lue talu + Tamil taml + Tai_Viet tavt + Telugu '(tel2 telu) + Tifinagh tfng + Tagalog tglg + Thaana thaa + Thai thai + Tibetan tibt + Tirhuta tirh + Ugaritic ugar + Vai vai + Warang_Citi wara + Old_Persian xpeo + Cuneiform xsux + Yi yi + Inherited zinh + Common zyyy + Unknown zzzz))) + + +(define/contract (fromUnicode script) + ((option/c symbol?) . -> . symbol?) + (hash-ref UNICODE_SCRIPTS script #f)) + +(define-stub-stop forString) + +(define-stub-stop forCodePoints) + +(define RTL '( arab ;; Arabic + hebr ;; Hebrew + syrc ;; Syriac + thaa ;; Thaana + cprt ;; Cypriot Syllabary + khar ;; Kharosthi + phnx ;; Phoenician + |nko | ;; N'Ko + lydi ;; Lydian + avst ;; Avestan + armi ;; Imperial Aramaic + phli ;; Inscriptional Pahlavi + prti ;; Inscriptional Parthian + sarb ;; Old South Arabian + orkh ;; Old Turkic, Orkhon Runic + samr ;; Samaritan + mand ;; Mandaic, Mandaean + merc ;; Meroitic Cursive + mero ;; Meroitic Hieroglyphs + + ;; Unicode 7.0 (not listed on http://www.microsoft.com/typography/otspec/scripttags.htm) + mani ;; Manichaean + mend ;; Mende Kikakui + nbat ;; Nabataean + narb ;; Old North Arabian + palm ;; Palmyrene + phlp ;; Psalter Pahlavi + )) + +(define (direction script) + (if (memq script RTL) 'rtl 'ltr)) \ No newline at end of file diff --git a/fontland/fontland/struct.rkt b/fontland/fontland/struct.rkt new file mode 100644 index 00000000..722ee3dd --- /dev/null +++ b/fontland/fontland/struct.rkt @@ -0,0 +1,49 @@ +#lang debug racket + +(provide (all-defined-out)) + +(struct ttf-font (port decoded-tables src directory ft-face hb-font hb-buf crc get-head-table-proc) + #:mutable + #:property prop:custom-write + (λ (f p w?) (display + (format "<~a ~a>" + (object-name f) + (let-values ([(dir name _) (split-path (ttf-font-src f))]) + name)) p))) + +(struct woff-font ttf-font ()) + +(define (ft-face this) + (or (force (ttf-font-ft-face this)) (error 'ft-face-not-available))) + +(define (font-directory this) + (or (force (ttf-font-directory this)) (error 'directory-not-available))) + +(define (hb-font this) + (or (force (ttf-font-hb-font this)) (error 'hb-font-not-available))) + +(define (hb-buf this) + (or (force (ttf-font-hb-buf this)) (error 'hp-buf-not-available))) + +(struct glyph (id codepoints font is-mark? is-ligature? metrics) #:mutable + #:property prop:custom-write + (λ (g p w?) (display + (format "<~a ~a ~a>" + (object-name g) + (glyph-id g) + (glyph-font g)) p))) + +(struct ttf-glyph glyph ()) + +; glyphs = list of glyph ids in the subset +; mapping = of glyph ids to indexes in glyphs +(struct subset (font glyphs mapping) #:mutable) + +(struct cff-subset subset (cff strings charstrings gsubrs) #:mutable) + +(struct cff-glyph glyph (path _usedGsubrs _usedSubrs) #:mutable) + +(define (make-cff-glyph . args) + (apply cff-glyph (append args (list #f (make-hash) (make-hash))))) + +(struct index-item (offset length) #:transparent #:mutable) \ No newline at end of file diff --git a/fontland/fontland/subset.rkt b/fontland/fontland/subset.rkt new file mode 100644 index 00000000..8cc4c38f --- /dev/null +++ b/fontland/fontland/subset.rkt @@ -0,0 +1,307 @@ +#lang debug racket/base +(require racket/serialize + racket/list + racket/match + sugar/unstable/dict + "table/loca.rkt" + "table-stream.rkt" + "directory.rkt" + "struct.rkt" + fontland/glyph + fontland/ttf-glyph + fontland/cff-glyph + xenomorph + racket/dict + fontland/table/cff/cff-font + fontland/table/cff/cff-top + fontland/table/cff/cff-standard-strings) + +(provide subset +subset + ttf-subset +ttf-subset ttf-subset? + cff-subset +cff-subset cff-subset? + subset-add-glyph! encode-to-port create-subset) + +#| +from +https://github.com/mbutterick/fontkit/blob/master/src/TTFFont.js +|# + +(define (create-subset font) + ((if (has-table? font #"CFF_") + +cff-subset + +ttf-subset) font)) + +#| +approximates +https://github.com/mbutterick/fontkit/blob/master/src/subset/Subset.js +|# + +(define (+subset font [glyphs empty] [mapping (mhash)]) + (define ss (subset font glyphs mapping)) + (subset-add-glyph! ss 0) + ss) + +(define (encode-to-port ss) + (define p (open-output-bytes)) + ((if (cff-subset? ss) + cff-subset-encode + ttf-subset-encode) ss p) + p) + +(define (subset-add-glyph! ss glyph-or-gid) ; fka `includeGlyph` + (define new-gid ((if (glyph? glyph-or-gid) glyph-id values) glyph-or-gid)) + ;; put the new glyph at the end of `glyphs`, + ;; and put its index in the mapping + (hash-ref! (subset-mapping ss) new-gid + (λ () + (set-subset-glyphs! ss (append (subset-glyphs ss) (list new-gid))) + (sub1 (length (subset-glyphs ss)))))) + + +#| +approximates +https://github.com/mbutterick/fontkit/blob/master/src/subset/CFFSubset.js +|# + + +(define (+cff-subset font [glyphs empty] [mapping (mhash)] + [cff (get-table font 'CFF_)] + [strings #f] + [charstrings #f] + [gsubrs #f]) + (define ss (cff-subset font glyphs mapping cff strings charstrings gsubrs)) + (subset-add-glyph! ss 0) + ss) + +(require racket/format racket/string) +(define (bytes->hexes bs) + (string-join + (for/list ([b (in-bytes bs)]) + (~r #:base 16 b #:min-width 2 #:pad-string "0"))  " ")) + +(define (subset-charstrings this) + (set-cff-subset-charstrings! this null) + (define gsubrs (make-hasheq)) + (for ([gid (in-list (subset-glyphs this))]) + (set-cff-subset-charstrings! + this + (append (cff-subset-charstrings this) + (list (get-char-string (cff-subset-cff this) gid)))) + + (define glyph (get-glyph (subset-font this) gid)) + (unless (cff-glyph-path glyph) (getPath glyph)) ;; this causes the glyph to be parsed + + (for ([subr (in-hash-keys (cff-glyph-_usedGsubrs glyph))]) + (hash-set! gsubrs subr #true))) + + (set-cff-subset-gsubrs! this (subset-subrs + this + (hash-ref (cff-subset-cff this) 'globalSubrIndex) + gsubrs))) + +(define (subset-subrs this subrs used) + (for/vector ([(subr i) (in-indexed subrs)]) + (cond + [(hash-ref used i #false) + (pos (hash-ref (cff-subset-cff this) 'stream) (index-item-offset subr)) + (read-bytes (index-item-length subr) (hash-ref (cff-subset-cff this) 'stream))] + [else (bytes 11)]))) + + +(define (subset-font-dict this topDict) + (error 'subsetFontdict-unimplemented)) + + +(define (create-cid-fontdict this top-dict) + (define used-subrs (make-hasheq)) + (for ([gid (in-list (subset-glyphs this))]) + (define glyph (get-glyph (subset-font this) gid)) + (unless (cff-glyph-path glyph) (getPath glyph)) ;; this causes the glyph to be parsed + + (for ([subr (in-hash-keys (cff-glyph-_usedSubrs glyph))]) + (hash-set! used-subrs subr #true))) + + (define cff-topDict (hash-ref (cff-subset-cff this) 'topDict)) + (define private-dict (hash-copy (hash-ref cff-topDict 'Private (make-hasheq)))) + (when (and (hash-has-key? cff-topDict 'Private) (hash-has-key? (hash-ref cff-topDict 'Private) 'Subrs)) + (hash-set! private-dict 'Subrs (subset-subrs this + (hash-ref (hash-ref cff-topDict 'Private) 'Subrs) + used-subrs))) + + (hash-set! top-dict 'FDArray (list (dictify 'Private private-dict))) + (hash-set! top-dict 'FDSelect (dictify 'version 3 + 'nRanges 1 + 'ranges (list (dictify 'first 0 'fd 0)) + 'sentinel (length (cff-subset-charstrings this)))) + (hash-ref top-dict 'FDSelect)) + +(define (add-string this [string #f]) + (cond + [(not string) #false] + [else + (unless (cff-subset-strings this) + (set-cff-subset-strings! this null)) + + (set-cff-subset-strings! this + (append (cff-subset-strings this) (list string))) + (+ (vector-length standard-strings) (sub1 (length (cff-subset-strings this))))])) + + +(define (cff-subset-encode this stream) + (subset-charstrings this) + + (define charset + (dictify 'version (if (> (length (cff-subset-charstrings this)) 255) 2 1) + 'ranges (list (dictify 'first 1 'nLeft (- (length (cff-subset-charstrings this)) 2))))) + + (define top-dict (hash-copy (hash-ref (cff-subset-cff this) 'topDict))) + (hash-set*! top-dict + 'Private #false + 'charset charset + 'Encoding #false + 'CharStrings (cff-subset-charstrings this)) + + (for ([key (in-list '(version Notice Copyright FullName + FamilyName Weight PostScript + BaseFontName FontName))]) + (hash-update! top-dict key + (λ (tdk-val) (add-string this (CFFont-string (cff-subset-cff this) tdk-val))))) + (hash-set! top-dict 'ROS (list (add-string this "Adobe") (add-string this "Identity") 0)) + (hash-set! top-dict 'CIDCount (length (cff-subset-charstrings this))) + + (if (hash-ref (cff-subset-cff this) 'isCIDFont) + (subset-font-dict this top-dict) + (create-cid-fontdict this top-dict)) + + (define top + (mhasheq 'version 1 + 'hdrSize (hash-ref (cff-subset-cff this) 'hdrSize) + 'offSize 4 + 'header (hash-ref (cff-subset-cff this) 'header #f) + 'nameIndex (list (CFFFont-postscriptName (cff-subset-cff this))) + 'topDictIndex (list top-dict) + 'stringIndex (cff-subset-strings this) + 'globalSubrIndex (cff-subset-gsubrs this))) + + (for ([k (in-list (sort (dict-keys top-dict) symbolstring fira-otf-path))) + (define cffss (+cff-subset otf)) + cffss) + +#| +approximates +https://github.com/mbutterick/fontkit/blob/master/src/subset/TTFSubset.js +|# + +(struct ttf-subset subset (glyf offset loca hmtx) #:transparent #:mutable) + +(define (+ttf-subset font [glyphs empty] [mapping (mhash)] + [glyf #f] + [offset #f] + [loca #f] + [hmtx #f]) + (define ss (ttf-subset font glyphs mapping glyf offset loca hmtx)) + (subset-add-glyph! ss 0) + ss) + + +(define (ttf-subset-add-glyph ss gid) + ;; glyph-decode unpacks the `glyf` table data corresponding to a certin gid. + ;; here, it's not necessary for non-composite glyphs + ;; because they just get copied entirely into the subset. + ;; it's just used to detect composite glyphs and handle them specially. + ;; so an optimization would be to detect composite / noncomposite without full glyph-decode. + (define glyph (get-glyph (subset-font ss) gid)) + (define ttf-glyf-data (glyph-decode glyph)) + + ;; get the offset to the glyph from the loca table + (match-define (list this-offset next-offset) + (take (drop (hash-ref (get-table (subset-font ss) 'loca) 'offsets) gid) 2)) + + (define port (get-table-stream (subset-font ss) 'glyf)) + (pos port (+ (pos port) this-offset)) + (define glyf-bytes (read-bytes (- next-offset this-offset) port)) + + ;; if it is a compound glyph, include its components + (when (and ttf-glyf-data (negative? (hash-ref ttf-glyf-data 'numberOfContours))) + (for ([ttf-glyph-component (in-list (hash-ref ttf-glyf-data 'components))]) + (define gid (subset-add-glyph! ss (ttf-glyph-component-glyph-id ttf-glyph-component))) + ;; note: this (ttf-glyph-component-pos component) is correct. It's a field of a Component object, not a port + (bytes-copy! glyf-bytes (ttf-glyph-component-pos ttf-glyph-component) (encode uint16be gid #f)))) + + ;; `loca` table v0 stores offsets as half of actual value + ;; so we need an even number of bytes to encode + (define glyf-bytes-even (if (odd? (bytes-length glyf-bytes)) + (bytes-append glyf-bytes #"0") + glyf-bytes)) + + (set-ttf-subset-glyf! ss (append (ttf-subset-glyf ss) (list glyf-bytes-even))) + (hash-update! (ttf-subset-loca ss) 'offsets + (λ (os) + (append os (list (ttf-subset-offset ss))))) + (hash-update! (ttf-subset-hmtx ss) 'metrics + (λ (ms) (append ms + (list (mhash 'advance (glyph-advance-width glyph) + 'bearing (hash-ref (get-glyph-metrics glyph) 'leftBearing)))))) + (set-ttf-subset-offset! ss (+ (ttf-subset-offset ss) (bytes-length glyf-bytes-even))) + (sub1 (length (ttf-subset-glyf ss)))) + +;; tables required by PDF spec: +;; head, hhea, loca, maxp, cvt, prep, glyf, hmtx, fpgm +;; additional tables required for standalone fonts: +;; name, cmap, OS/2, post + +(define (clone-deep val) (deserialize (serialize val))) + +(define (ttf-subset-encode ss port) + (set-ttf-subset-glyf! ss empty) + (set-ttf-subset-offset! ss 0) + (set-ttf-subset-loca! ss (mhash 'offsets empty)) + (set-ttf-subset-hmtx! ss (mhash 'metrics empty 'bearings empty)) + + ;; include all the glyphs used in the document + ;; not using `in-list` because we need to support adding more + ;; glyphs to the array as component glyphs are discovered & enqueued + (for ([idx (in-naturals)] + #:break (= idx (length (subset-glyphs ss)))) + (define gid (list-ref (subset-glyphs ss) idx)) + (ttf-subset-add-glyph ss gid)) + + (define new-maxp-table (clone-deep (get-maxp-table (subset-font ss)))) + (dict-set! new-maxp-table 'numGlyphs (length (ttf-subset-glyf ss))) + + ;; populate the new loca table + (dict-update! (ttf-subset-loca ss) 'offsets (λ (vals) (append vals (list (ttf-subset-offset ss))))) + (loca-pre-encode (ttf-subset-loca ss)) + + (define new-head-table (clone-deep (get-head-table (subset-font ss)))) + (dict-set! new-head-table 'indexToLocFormat (dict-ref (ttf-subset-loca ss) x:version-key)) + + (define new-hhea-table (clone-deep (get-hhea-table (subset-font ss)))) + (dict-set! new-hhea-table 'numberOfMetrics (length (dict-ref (ttf-subset-hmtx ss) 'metrics))) + + (define new-tables + (filter cdr (dictify 'head new-head-table + 'hhea new-hhea-table + 'loca (ttf-subset-loca ss) + 'maxp new-maxp-table + 'cvt_ (and (has-table? (subset-font ss) 'cvt_) + (get-cvt_-table (subset-font ss))) + 'prep (and (has-table? (subset-font ss) 'prep) + (get-prep-table (subset-font ss))) + 'glyf (ttf-subset-glyf ss) + 'hmtx (ttf-subset-hmtx ss) + 'fpgm (and (has-table? (subset-font ss) 'fpgm) + (get-fpgm-table (subset-font ss)))))) + + (encode directory (mhash 'tables new-tables) port) + (void)) + diff --git a/fontland/fontland/table-stream.rkt b/fontland/fontland/table-stream.rkt new file mode 100644 index 00000000..f4092c89 --- /dev/null +++ b/fontland/fontland/table-stream.rkt @@ -0,0 +1,58 @@ +#lang debug racket +(require xenomorph + "tables.rkt" + "struct.rkt" + "zlib.rkt" + (for-syntax "tables.rkt")) +(provide (all-defined-out)) + +(define-syntax (define-table-getters stx) + (syntax-case stx () + [(_) + (with-syntax ([(TABLE-TAG ...) (hash-keys table-codecs)]) + (with-syntax ([(GETTER-ID ...) (map (λ (tag) (datum->syntax stx (string->symbol (format "get-~a-table" (syntax->datum tag))))) + (syntax->list #'(TABLE-TAG ...)))] + [(HAS-ID? ...) (map (λ (tag) (datum->syntax stx (string->symbol (format "has-~a-table?" (syntax->datum tag))))) + (syntax->list #'(TABLE-TAG ...)))]) + #'(begin + (define (GETTER-ID this) (get-table this 'TABLE-TAG)) ... + (define (HAS-ID? this) (has-table? this 'TABLE-TAG)) ...)))])) + +(define (has-table? this tag) + #;((or/c bytes? symbol?) . ->m . boolean?) + (define directory (force (ttf-font-directory this))) + (hash-has-key? (hash-ref directory 'tables) (match tag + [(? bytes?) (string->symbol (bytes->string/latin-1 tag))] + [_ tag]))) + +(define (get-table this table-tag) + (unless (has-table? this table-tag) + (raise-argument-error 'get-table "table that exists in font" table-tag)) + (hash-ref! (ttf-font-decoded-tables this) table-tag (λ () (decode-table this table-tag)))) + +(define-table-getters) + +(define (get-table-stream this tag) + (define directory (force (ttf-font-directory this))) + (define table (hash-ref (hash-ref directory 'tables) tag)) + (and table + (pos (ttf-font-port this) (hash-ref table 'offset)) + (if (and (woff-font? this) (< (hash-ref table 'compLength) (hash-ref table 'length))) + (open-input-bytes (inflate (peek-bytes (hash-ref table 'compLength) 0 (ttf-font-port this)))) + (ttf-font-port this)))) + +(define (decode-table this table-tag) + (unless (hash-has-key? table-codecs table-tag) + (raise-argument-error 'decode-table "decodable table" table-tag)) + + (define last-pos (pos (ttf-font-port this))) + (define stream (get-table-stream this table-tag)) + + (define table-decoder (hash-ref table-codecs table-tag)) + (define directory (force (ttf-font-directory this))) + (define table (hash-ref (hash-ref directory 'tables) table-tag)) + + (begin0 + (decode table-decoder stream #:parent this (hash-ref table 'length)) + (pos (ttf-font-port this) last-pos))) + diff --git a/fontland/fontland/table/CFF_.rkt b/fontland/fontland/table/CFF_.rkt new file mode 100644 index 00000000..74fd830a --- /dev/null +++ b/fontland/fontland/table/CFF_.rkt @@ -0,0 +1,5 @@ +#lang debug racket/base +(require "cff/cff-font.rkt") + +;; the CFFFont object acts as the decoder for the `CFF ` table. +(provide (rename-out [CFFFont CFF_])) diff --git a/fontland/fontland/table/OS2.rkt b/fontland/fontland/table/OS2.rkt new file mode 100644 index 00000000..814b6639 --- /dev/null +++ b/fontland/fontland/table/OS2.rkt @@ -0,0 +1,73 @@ +#lang racket/base +(require xenomorph sugar/unstable/dict) +(provide OS/2) + +#| +approximates +https://github.com/mbutterick/fontkit/blob/master/src/tables/OS2.js +|# + +(define OS/2 (let () + (define type-1 + (dictify 'typoAscender int16be + 'typoDescender int16be + 'typoLineGap int16be + 'winAscent uint16be + 'winDescent uint16be + 'codePageRange (x:array #:type uint32be #:length 2))) + + (define type-2 + (dictify 'xHeight int16be + 'capHeight int16be + 'defaultChar uint16be + 'breakChar uint16be + 'maxContent uint16be)) + + (define type-5 + (dictify 'usLowerOpticalPointSize uint16be + 'usUpperOpticalPointSize uint16be)) + + (x:versioned-struct + uint16be + (dictify + 'header (dictify 'xAvgCharWidth int16be ;; average weighted advance width of lower case letters and space + 'usWeightClass uint16be ;; visual weight of stroke in glyphs + 'usWidthClass uint16be ;; relative change from the normal aspect ratio (width to height ratio) + ;; Indicates font embedding licensing rights + 'fsType (x:bitfield #:type uint16be + #:flags '(#f noEmbedding viewOnly editable #f #f #f #f noSubsetting bitmapOnly)) + 'ySubscriptXSize int16be ;; recommended horizontal size in pixels for subscripts + 'ySubscriptYSize int16be ;; recommended vertical size in pixels for subscripts + 'ySubscriptXOffset int16be ;; recommended horizontal offset for subscripts + 'ySubscriptYOffset int16be ;; recommended vertical offset form the baseline for subscripts + 'ySuperscriptXSize int16be ;; recommended horizontal size in pixels for superscripts + 'ySuperscriptYSize int16be ;; recommended vertical size in pixels for superscripts + 'ySuperscriptXOffset int16be ;; recommended horizontal offset for superscripts + 'ySuperscriptYOffset int16be ;; recommended vertical offset from the baseline for superscripts + 'yStrikeoutSize int16be ;; width of the strikeout stroke + 'yStrikeoutPosition int16be ;; position of the strikeout stroke relative to the baseline + 'sFamilyClass int16be ;; classification of font-family design + 'panose (x:array #:type uint8 #:length 10) ;; describe the visual characteristics of a given typeface + 'ulCharRange (x:array #:type uint32be #:length 4) + 'vendorID (x:symbol #:length 4) ;; four character identifier for the font vendor + ;; bit field containing information about the font + 'fsSelection (x:bitfield #:type uint16 + #:flags '(italic underscore negative outlined strikeout bold regular useTypoMetrics wws oblique)) + 'usFirstCharIndex uint16be ;; The minimum Unicode index in this font + 'usLastCharIndex uint16be) ;; The maximum Unicode index in this font + + 0 null + 1 type-1 + 2 (append type-1 type-2) + 3 (append type-1 type-2) + 4 (append type-1 type-2) + 5 (append type-1 type-2 type-5))))) + +(module+ test + (require rackunit racket/serialize "../helper.rkt") + (define ip (open-input-file charter-path)) + (define dir (deserialize (read (open-input-file charter-directory-path)))) + (define offset (hash-ref (hash-ref (hash-ref dir 'tables) 'OS/2) 'offset)) + (define len (hash-ref (hash-ref (hash-ref dir 'tables) 'OS/2) 'length)) + (check-equal? offset 360) + (check-equal? len 96)) \ No newline at end of file diff --git a/fontland/fontland/table/cff/cff-charsets.rkt b/fontland/fontland/table/cff/cff-charsets.rkt new file mode 100644 index 00000000..b54b0e1b --- /dev/null +++ b/fontland/fontland/table/cff/cff-charsets.rkt @@ -0,0 +1,104 @@ +#lang racket/base +(provide (all-defined-out)) + +#| +approximates +https://github.com/mbutterick/fontkit/blob/master/src/cff/CFFCharsets.js +|# + +(define ISOAdobeCharset + '(.notdef space exclam quotedbl numbersign dollar + percent ampersand quoteright parenleft parenright + asterisk plus comma hyphen period slash zero + one two three four five six seven eight + nine colon semicolon less equal greater question + at A B C D E F G H I J K L M + N O P Q R S T U V W X Y Z + bracketleft backslash bracketright asciicircum underscore + quoteleft a b c d e f g h i j k l + m n o p q r s t u v w x y z + braceleft bar braceright asciitilde exclamdown cent + sterling fraction yen florin section currency + quotesingle quotedblleft guillemotleft guilsinglleft + guilsinglright fi fl endash dagger daggerdbl + periodcentered paragraph bullet quotesinglbase + quotedblbase quotedblright guillemotright ellipsis + perthousand questiondown grave acute circumflex tilde + macron breve dotaccent dieresis ring cedilla + hungarumlaut ogonek caron emdash AE ordfeminine + Lslash Oslash OE ordmasculine ae dotlessi lslash + oslash oe germandbls onesuperior logicalnot mu + trademark Eth onehalf plusminus Thorn onequarter + divide brokenbar degree thorn threequarters twosuperior + registered minus eth multiply threesuperior copyright + Aacute Acircumflex Adieresis Agrave Aring Atilde + Ccedilla Eacute Ecircumflex Edieresis Egrave Iacute + Icircumflex Idieresis Igrave Ntilde Oacute Ocircumflex + Odieresis Ograve Otilde Scaron Uacute Ucircumflex + Udieresis Ugrave Yacute Ydieresis Zcaron aacute + acircumflex adieresis agrave aring atilde ccedilla + eacute ecircumflex edieresis egrave iacute icircumflex + idieresis igrave ntilde oacute ocircumflex odieresis + ograve otilde scaron uacute ucircumflex udieresis + ugrave yacute ydieresis zcaron)) + +(define ExpertCharset + '(.notdef space exclamsmall Hungarumlautsmall dollaroldstyle + dollarsuperior ampersandsmall Acutesmall parenleftsuperior + parenrightsuperior twodotenleader onedotenleader comma + hyphen period fraction zerooldstyle oneoldstyle + twooldstyle threeoldstyle fouroldstyle fiveoldstyle + sixoldstyle sevenoldstyle eightoldstyle nineoldstyle + colon semicolon commasuperior threequartersemdash + periodsuperior questionsmall asuperior bsuperior + centsuperior dsuperior esuperior isuperior lsuperior + msuperior nsuperior osuperior rsuperior ssuperior + tsuperior ff fi fl ffi ffl parenleftinferior + parenrightinferior Circumflexsmall hyphensuperior Gravesmall + Asmall Bsmall Csmall Dsmall Esmall Fsmall Gsmall + Hsmall Ismall Jsmall Ksmall Lsmall Msmall Nsmall + Osmall Psmall Qsmall Rsmall Ssmall Tsmall Usmall + Vsmall Wsmall Xsmall Ysmall Zsmall colonmonetary + onefitted rupiah Tildesmall exclamdownsmall centoldstyle + Lslashsmall Scaronsmall Zcaronsmall Dieresissmall + Brevesmall Caronsmall Dotaccentsmall Macronsmall + figuredash hypheninferior Ogoneksmall Ringsmall + Cedillasmall onequarter onehalf threequarters + questiondownsmall oneeighth threeeighths fiveeighths + seveneighths onethird twothirds zerosuperior onesuperior + twosuperior threesuperior foursuperior fivesuperior + sixsuperior sevensuperior eightsuperior ninesuperior + zeroinferior oneinferior twoinferior threeinferior + fourinferior fiveinferior sixinferior seveninferior + eightinferior nineinferior centinferior dollarinferior + periodinferior commainferior Agravesmall Aacutesmall + Acircumflexsmall Atildesmall Adieresissmall Aringsmall + AEsmall Ccedillasmall Egravesmall Eacutesmall + Ecircumflexsmall Edieresissmall Igravesmall Iacutesmall + Icircumflexsmall Idieresissmall Ethsmall Ntildesmall + Ogravesmall Oacutesmall Ocircumflexsmall Otildesmall + Odieresissmall OEsmall Oslashsmall Ugravesmall Uacutesmall + Ucircumflexsmall Udieresissmall Yacutesmall Thornsmall + Ydieresissmall)) + +(define ExpertSubsetCharset + '(.notdef space dollaroldstyle dollarsuperior + parenleftsuperior parenrightsuperior twodotenleader + onedotenleader comma hyphen period fraction + zerooldstyle oneoldstyle twooldstyle threeoldstyle + fouroldstyle fiveoldstyle sixoldstyle sevenoldstyle + eightoldstyle nineoldstyle colon semicolon commasuperior + threequartersemdash periodsuperior asuperior bsuperior + centsuperior dsuperior esuperior isuperior lsuperior + msuperior nsuperior osuperior rsuperior ssuperior + tsuperior ff fi fl ffi ffl parenleftinferior + parenrightinferior hyphensuperior colonmonetary onefitted + rupiah centoldstyle figuredash hypheninferior onequarter + onehalf threequarters oneeighth threeeighths fiveeighths + seveneighths onethird twothirds zerosuperior onesuperior + twosuperior threesuperior foursuperior fivesuperior + sixsuperior sevensuperior eightsuperior ninesuperior + zeroinferior oneinferior twoinferior threeinferior + fourinferior fiveinferior sixinferior seveninferior + eightinferior nineinferior centinferior dollarinferior + periodinferior commainferior)) \ No newline at end of file diff --git a/fontland/fontland/table/cff/cff-dict.rkt b/fontland/fontland/table/cff/cff-dict.rkt new file mode 100644 index 00000000..9ff220f6 --- /dev/null +++ b/fontland/fontland/table/cff/cff-dict.rkt @@ -0,0 +1,135 @@ +#lang debug racket/base +(require racket/class + racket/match + racket/list + racket/dict + xenomorph + sugar/unstable/dict + "cff-operand.rkt") +(provide CFFDict) + +#| +approximates +https://github.com/mbutterick/fontkit/blob/master/src/cff/CFFDict.js +|# + +(define (op->key op) + (match (car op) + [(list* x0 x1 _) (bitwise-ior (arithmetic-shift x0 8) x1)] + [val val])) + +(define CFFDict% + (class x:base% + (super-new) + (init-field [(@name name)] [(@ops ops)]) + + (field [(@fields fields) + (for/hash ([field (in-list @ops)]) + (values (op->key field) field))]) + + (define (decode-operands type stream ret operands) + (match type + [(? list?) (for/list ([op (in-list operands)] + [subtype (in-list type)]) + (decode-operands subtype stream ret (list op)))] + [(? xenomorphic?) (send type x:decode stream ret operands)] + [(or 'number 'offset 'sid) (car operands)] + ['boolean (if (car operands) #t #f)] + [_ operands])) + + (define (encode-operands type stream ctx operands) + (match type + [(? list?) + (for/list ([op (in-list operands)] + [subtype (in-list type)]) + (car (encode-operands subtype stream ctx op)))] + [(? xenomorphic?) type (send type x:encode operands stream ctx)] + [_ (match operands + [(? number?) (list operands)] + [(? boolean?) (list (if operands 1 0))] + [(? list?) operands] + [_ (list operands)])])) + + (define/override (post-decode val) + (dict->mutable-hash val)) + + (define/augment (x:decode stream parent) + (define end (+ (pos stream) (hash-ref parent 'length))) + (define ret (make-hasheq)) + + ;; define hidden properties + (hash-set! ret x:parent-key parent) + (hash-set! ret x:start-offset-key (pos stream)) + + ;; fill in defaults + (for ([(key field) (in-hash @fields)]) + (hash-set! ret (second field) (fourth field))) + + (let loop ([operands null]) + (when (< (pos stream) end) + (define b (read-byte stream)) + (let bloop ([b b]) + (cond + [(< b 28) + (let ([b (if (= b 12) + (bitwise-ior (arithmetic-shift b 8) (read-byte stream)) + b)]) + (match (hash-ref @fields b #false) + [#false (error 'cff-dict-decode (format "unknown operator: ~a" b))] + [field + (define val (decode-operands (third field) stream ret operands)) + (unless (void? val) + (hash-set! ret (second field) val)) + (loop null)]))] + [else + (loop (append operands (list (decode CFFOperand stream b))))])))) + + ret) + + (define/augment (x:size dict parent [include-pointers #true]) + (define ctx + (mhasheq x:parent-key parent + x:val-key dict + x:pointer-size-key 0 + x:start-offset-key (hash-ref parent x:start-offset-key 0))) + + (+ (for*/sum ([k (in-list (sort (dict-keys @fields) <))] + [field (in-value (dict-ref @fields k))] + [val (in-value (dict-ref dict (second field) #false))] + #:when (and val (not (equal? val (fourth field))))) + (define operands (encode-operands (third field) #false ctx val)) + (define operand-size (for/sum ([op (in-list operands)]) + (send CFFOperand x:size op))) + (define key (if (list? (first field)) (first field) (list (first field)))) + (+ operand-size (length key))) + (if include-pointers (hash-ref ctx x:pointer-size-key) 0))) + + (define/augment (x:encode dict stream parent) + (define ctx (mhasheq + x:pointers-key null + x:start-offset-key (pos stream) + x:parent-key parent + x:val-key dict + x:pointer-size-key 0)) + + (hash-set! ctx x:pointer-offset-key (+ (pos stream) (x:size dict ctx #false))) + + (for ([field (in-list @ops)]) + (match-define (list f0 f1 f2 f3) field) + (match (dict-ref dict f1 #false) + [(or #false (== f3)) (void)] + [val + (define operands (encode-operands f2 stream ctx val)) + (for ([op (in-list operands)]) + (send CFFOperand x:encode op stream)) + (define key (if (list? f0) f0 (list f0))) + (for ([op (in-list key)]) + (encode uint8 op stream))])) + + (let loop ([i 0]) + (when (< i (length (hash-ref ctx x:pointers-key))) + (match (list-ref (hash-ref ctx x:pointers-key) i) + [(x:ptr type val parent) (send type x:encode val stream parent)]) + (loop (add1 i))))))) + +(define (CFFDict [name 'unknown] [ops null]) (make-object CFFDict% name ops)) \ No newline at end of file diff --git a/fontland/fontland/table/cff/cff-encodings.rkt b/fontland/fontland/table/cff/cff-encodings.rkt new file mode 100644 index 00000000..e990c95b --- /dev/null +++ b/fontland/fontland/table/cff/cff-encodings.rkt @@ -0,0 +1,52 @@ +#lang racket/base +(provide (all-defined-out)) + +#| +approximates +https://github.com/mbutterick/fontkit/blob/master/src/cff/CFFEncodings.js +|# + +(define StandardEncoding '(|| || || || || || || || || || || || || || || || || || || || || || || || || || || || + || || || || space exclam quotedbl numbersign dollar percent ampersand quoteright + parenleft parenright asterisk plus comma hyphen period slash zero one two + three four five six seven eight nine colon semicolon less equal greater + question at A B C D E F G H I J K L M N O P Q R S + T U V W X Y Z bracketleft backslash bracketright asciicircum underscore + quoteleft a b c d e f g h i j k l m n o p q r s t + u v w x y z braceleft bar braceright asciitilde || || || || || || || || + || || || || || || || || || || || || || || || || || || || || || || || || || || + exclamdown cent sterling fraction yen florin section currency quotesingle + quotedblleft guillemotleft guilsinglleft guilsinglright fi fl || endash dagger + daggerdbl periodcentered || paragraph bullet quotesinglbase quotedblbase quotedblright + guillemotright ellipsis perthousand || questiondown || grave acute circumflex tilde + macron breve dotaccent dieresis || ring cedilla || hungarumlaut ogonek caron + emdash || || || || || || || || || || || || || || || || AE || ordfeminine || || || + || Lslash Oslash OE ordmasculine || || || || || ae || || || dotlessi || || + lslash oslash oe germandbls)) + +(define ExpertEncoding '(|| || || || || || || || || || || || || || || || || || || || || || || || || || || || + || || || || space exclamsmall Hungarumlautsmall || dollaroldstyle dollarsuperior + ampersandsmall Acutesmall parenleftsuperior parenrightsuperior twodotenleader onedotenleader + comma hyphen period fraction zerooldstyle oneoldstyle twooldstyle threeoldstyle + fouroldstyle fiveoldstyle sixoldstyle sevenoldstyle eightoldstyle nineoldstyle colon + semicolon commasuperior threequartersemdash periodsuperior questionsmall || asuperior + bsuperior centsuperior dsuperior esuperior || || isuperior || || lsuperior msuperior + nsuperior osuperior || || rsuperior ssuperior tsuperior || ff fi fl ffi ffl + parenleftinferior || parenrightinferior Circumflexsmall hyphensuperior Gravesmall Asmall + Bsmall Csmall Dsmall Esmall Fsmall Gsmall Hsmall Ismall Jsmall Ksmall Lsmall + Msmall Nsmall Osmall Psmall Qsmall Rsmall Ssmall Tsmall Usmall Vsmall Wsmall + Xsmall Ysmall Zsmall colonmonetary onefitted rupiah Tildesmall || || || || || || || + || || || || || || || || || || || || || || || || || || || || || || || || || || || + exclamdownsmall centoldstyle Lslashsmall || || Scaronsmall Zcaronsmall Dieresissmall + Brevesmall Caronsmall || Dotaccentsmall || || Macronsmall || || figuredash hypheninferior + || || Ogoneksmall Ringsmall Cedillasmall || || || onequarter onehalf threequarters + questiondownsmall oneeighth threeeighths fiveeighths seveneighths onethird twothirds || + || zerosuperior onesuperior twosuperior threesuperior foursuperior fivesuperior + sixsuperior sevensuperior eightsuperior ninesuperior zeroinferior oneinferior twoinferior + threeinferior fourinferior fiveinferior sixinferior seveninferior eightinferior + nineinferior centinferior dollarinferior periodinferior commainferior Agravesmall + Aacutesmall Acircumflexsmall Atildesmall Adieresissmall Aringsmall AEsmall Ccedillasmall + Egravesmall Eacutesmall Ecircumflexsmall Edieresissmall Igravesmall Iacutesmall + Icircumflexsmall Idieresissmall Ethsmall Ntildesmall Ogravesmall Oacutesmall + Ocircumflexsmall Otildesmall Odieresissmall OEsmall Oslashsmall Ugravesmall Uacutesmall + Ucircumflexsmall Udieresissmall Yacutesmall Thornsmall Ydieresissmall)) \ No newline at end of file diff --git a/fontland/fontland/table/cff/cff-font.rkt b/fontland/fontland/table/cff/cff-font.rkt new file mode 100644 index 00000000..1e869bb6 --- /dev/null +++ b/fontland/fontland/table/cff/cff-font.rkt @@ -0,0 +1,134 @@ +#lang debug racket/base +(require racket/class racket/match racket/list xenomorph + "cff-top.rkt" + "cff-standard-strings.rkt" + fontland/struct) +(provide (all-defined-out)) + +#| +approximates +https://github.com/mbutterick/fontkit/blob/master/src/cff/CFFFont.js +|# + +;; the CFFFont object acts as the decoder for the `CFF ` table. +;; so it should return a hash. + +(define CFFFont% + (class x:base% + (super-new) + + (define/augride (x:decode stream parent . _) + (define cff-font (make-hasheq)) + (hash-set! cff-font 'stream stream) + + (for ([(k v) (in-hash (decode CFFTop stream))]) + (hash-set! cff-font k v)) + + (when (and (hash-has-key? cff-font 'version) (< (hash-ref cff-font 'version) 2)) + (match (hash-ref cff-font 'topDictIndex) + [(vector dict) (hash-set! cff-font 'topDict dict)] + [_ (error 'only-single-font-allowed-in-cff)])) + + (hash-set! cff-font 'isCIDFont (hash-ref (hash-ref cff-font 'topDict) 'ROS)) + cff-font))) + +(define (CFFont-string this sid) + (cond + [(not sid) #false] + [(>= (hash-ref this 'version) 2) #false] + [(< sid (vector-length standard-strings)) (vector-ref standard-strings sid)] + [else (vector-ref (hash-ref this 'stringIndex) (- sid (vector-length standard-strings)))])) + +(define (CFFFont-postscriptName this) + (and (< (hash-ref this 'version) 2) (vector-ref (hash-ref this 'nameIndex) 0))) + +(define CFFFont (make-object CFFFont%)) + +(define (get-char-string cff-font glyph-id) + (define glyph-record (vector-ref (hash-ref (hash-ref cff-font 'topDict) 'CharStrings) glyph-id)) + (pos (hash-ref cff-font 'stream) (index-item-offset glyph-record)) + (read-bytes (index-item-length glyph-record) (hash-ref cff-font 'stream))) + +(define (fd-for-glyph this gid) + (cond + [(not (hash-has-key? (hash-ref this 'topDict) 'FDSelect)) #false] + [else + (match (hash-ref* this 'topDict 'FDSelect 'version) + [0 (list-ref (hash-ref* this 'topDict 'FDSelect) gid)] + [(or 3 4) + (define ranges (hash-ref* this 'topDict 'FDSelect 'ranges)) + (let loop ([low 0][high (sub1 (length ranges))]) + (when (<= low high) + (define mid (arithmetic-shift (+ low high) -1)) + (cond + [(< gid (hash-ref (list-ref ranges mid) 'first)) + (loop low (sub1 mid))] + [(and (< mid high) (> gid (hash-ref (list-ref ranges (add1 mid)) 'first))) + (loop (add1 mid) high)] + [else (hash-ref (list-ref ranges mid) 'fd)])))] + [default (error 'unknown-select-version)])])) + +(define (private-dict-for-glyph this gid) + (cond + [(and (hash-has-key? this 'topDict) + (hash-has-key? (hash-ref this 'topDict) 'FDSelect) + (hash-ref* this 'topDict 'FDSelect)) + (define fd (fd-for-glyph this gid)) + (if (list-ref (hash-ref* this 'topDict 'FDArray) fd) + (hash-ref (list-ref (hash-ref* 'topDict 'FDArray) fd) 'Private) + #false)] + [(< (hash-ref this 'version) 2) (hash-ref* this 'topDict 'Private)] + [else (hash-ref (list-ref (hash-ref* this 'topDict 'FDArray) 0) 'Private)])) + +(module+ test + (require rackunit racket/serialize racket/stream fontland/helper) + (define dir (deserialize (read (open-input-file fira-otf-directory-path)))) + (define cff (hash-ref (hash-ref dir 'tables) 'CFF_)) + (check-equal? (hash-ref cff 'length) 164604) + (define ip (open-input-file fira-otf-path)) + (define cff-offset (hash-ref cff 'offset)) + (check-equal? cff-offset 33472) + (file-position ip cff-offset) + (define cff-font (decode CFFFont ip)) + (check-equal? (file-position (hash-ref cff-font 'stream)) 74651) + (check-equal? (hash-ref cff-font 'version) 1) + (check-equal? (hash-ref cff-font 'hdrSize) 4) + (check-equal? (hash-ref cff-font 'offSize) 3) + (check-equal? (hash-ref cff-font 'nameIndex) '#("FiraSans-Book")) + (check-equal? (vector-length (hash-ref cff-font 'globalSubrIndex)) 820) + (check-equal? + (for/list ([h (in-vector (hash-ref cff-font 'globalSubrIndex))]) + (index-item-offset h)) + '(60105 60130 60218 60264 60303 60330 60361 60366 60387 60427 60433 60447 60454 60469 60500 60506 60512 60516 60545 60566 60581 60624 60637 60667 60679 60705 60715 60755 60776 60781 60839 60891 60897 60907 60914 60920 60938 60950 60976 60992 61005 61011 61032 61051 61067 61097 61111 61172 61272 61284 61359 61430 61489 61522 61526 61531 61535 61543 61565 61570 61575 61579 61601 61615 61629 61649 61654 61664 61842 61849 61858 61865 61895 61913 61920 61964 61977 61996 62074 62094 62102 62128 62132 62149 62160 62170 62197 62216 62225 62230 62237 62247 62256 62285 62332 62339 62347 62350 62375 62435 62479 62511 62539 62561 62585 62605 62621 62632 62711 62717 62733 62743 62783 62809 62818 62868 62905 62955 62965 62971 63034 63050 63059 63191 63237 63358 63394 63460 63465 63592 63716 63740 63866 63924 63947 64051 64075 64099 64120 64184 64245 64260 64374 64493 64515 64543 64585 64592 64597 64611 64622 64735 64738 64789 64797 64882 64920 65027 65054 65057 65069 65077 65113 65125 65222 65254 65275 65377 65480 65516 65524 65530 65550 65565 65569 65576 65673 65691 65760 65836 65854 65866 65873 65881 65895 65924 65929 65949 65970 66060 66093 66113 66132 66146 66151 66160 66165 66174 66185 66192 66210 66231 66255 66280 66288 66296 66301 66386 66395 66400 66446 66455 66537 66545 66550 66555 66636 66712 66722 66729 66748 66774 66788 66797 66810 66818 66841 66847 66853 66872 66877 66882 66887 66962 66988 66997 67008 67021 67027 67034 67040 67047 67110 67180 67218 67256 67325 67355 67369 67376 67390 67399 67403 67471 67478 67499 67520 67524 67550 67565 67579 67584 67651 67671 67679 67684 67749 67759 67772 67783 67790 67817 67883 67944 67967 67986 68049 68056 68090 68113 68132 68139 68149 68154 68159 68222 68226 68259 68262 68323 68326 68335 68372 68413 68420 68427 68435 68441 68446 68451 68462 68477 68489 68530 68535 68548 68553 68560 68567 68622 68638 68694 68748 68759 68764 68816 68862 68880 68885 68900 68907 68959 68988 69002 69011 69016 69028 69037 69089 69099 69115 69131 69143 69152 69160 69168 69174 69180 69202 69213 69218 69268 69318 69325 69374 69383 69402 69415 69422 69427 69434 69446 69488 69514 69529 69535 69582 69587 69603 69647 69667 69678 69684 69690 69700 69705 69710 69752 69795 69816 69860 69888 69898 69912 69921 69932 69936 69943 69948 69991 70002 70028 70041 70051 70057 70099 70130 70151 70166 70207 70219 70257 70279 70290 70300 70309 70316 70325 70333 70341 70346 70352 70357 70364 70401 70438 70475 70480 70487 70491 70497 70502 70532 70545 70552 70557 70562 70599 70616 70647 70651 70658 70665 70670 70706 70712 70737 70754 70766 70778 70786 70798 70804 70812 70817 70823 70858 70893 70904 70908 70913 70926 70933 70947 70954 70962 70968 70976 70981 70987 70996 71002 71007 71012 71018 71041 71074 71079 71111 71143 71175 71192 71196 71207 71215 71220 71226 71234 71247 71254 71261 71291 71308 71314 71322 71339 71345 71350 71354 71379 71395 71404 71413 71417 71422 71436 71458 71463 71479 71491 71501 71509 71521 71528 71535 71541 71547 71555 71561 71568 71575 71582 71588 71594 71599 71626 71640 71666 71692 71696 71704 71722 71735 71750 71759 71766 71781 71792 71797 71802 71808 71813 71818 71840 71865 71890 71894 71905 71910 71916 71927 71935 71942 71949 71956 71961 71966 71977 72001 72014 72036 72044 72056 72070 72076 72082 72087 72093 72100 72112 72135 72151 72155 72163 72172 72177 72182 72195 72217 72239 72261 72277 72297 72304 72324 72337 72350 72355 72365 72370 72377 72387 72394 72398 72405 72412 72417 72423 72429 72446 72460 72465 72485 72495 72514 72523 72530 72535 72546 72566 72586 72604 72624 72642 72651 72660 72666 72678 72684 72689 72698 72707 72714 72722 72730 72735 72740 72746 72752 72758 72774 72793 72799 72812 72825 72834 72839 72858 72862 72868 72877 72886 72895 72904 72913 72922 72931 72940 72946 72953 72960 72967 72973 72978 72985 73000 73007 73025 73043 73048 73066 73073 73089 73100 73111 73118 73124 73135 73140 73146 73152 73158 73163 73168 73173 73186 73200 73217 73234 73249 73264 73279 73286 73302 73318 73332 73348 73358 73374 73384 73390 73395 73403 73413 73422 73428 73438 73445 73452 73459 73465 73473 73479 73486 73491 73497 73505 73513 73521 73528 73533 73538 73545 73552 73559 73566 73572 73587 73592 73604 73619 73634 73649 73664 73679 73694 73709 73722 73735 73743 73748 73753 73758 73771 73784 73798 73804 73814 73827 73839 73843 73851 73860 73865 73872 73881 73890 73899 73908 73914 73920 73926 73932 73938 73943 73948 73953 73963 73976 73989 74002 74015 74028 74037 74050 74063 74076 74087 74098 74104 74110 74117 74123 74130 74137 74144 74149 74156 74161 74166 74171 74176 74188 74200 74212 74224 74236 74248 74260 74266 74278 74282 74294 74306 74313 74321 74328 74336 74342 74350 74357 74364 74370 74377 74385 74390 74396 74402 74408 74414 74420 74425 74430 74435 74440 74445 74450 74455 74466 74477 74488 74498 74509 74520 74531 74542 74553 74564 74575 74584 74593 74602 74611 74616 74621 74626 74631 74636 74641 74646)) + (check-equal? (vector-length (hash-ref cff-font 'stringIndex)) 2404) + #;(check-equal? (hash-ref (hash-ref cff-font 'topDict) 'version) 2401) + (check-equal? + (vector-ref (hash-ref cff-font 'stringIndex) 2401) + "004.106") + (check-equal? + (vector-ref (hash-ref cff-font 'stringIndex) 2402) + "Digitized data copyright \\(c\\) 2012-2015, The Mozilla Foundation and Telefonica S.A.") + (check-equal? + (vector-ref (hash-ref cff-font 'stringIndex) 2403) + "Fira Sans Book") + + (define top-dict (hash-ref cff-font 'topDict)) + (check-equal? (hash-ref top-dict 'FontBBox) '(-167 -350 1360 1093)) + (check-equal? (hash-ref top-dict 'version) 2792) + (check-equal? (hash-ref top-dict 'Notice) 2793) + (check-equal? (hash-ref top-dict 'FullName) 2794) + (check-equal? (hash-ref top-dict 'Weight) 388) + + (define private (hash-ref top-dict 'Private)) + (check-equal? (hash-ref private 'StdHW) 68) + (check-equal? (hash-ref private 'StdVW) 84) + (check-equal? (hash-ref private 'defaultWidthX) 0) + (check-equal? (hash-ref private 'nominalWidthX) 553) + (check-equal? (hash-ref private 'BlueScale) 0.037) + (check-equal? (hash-ref private 'BlueShift) 7) + (check-equal? (hash-ref private 'ExpansionFactor) 0.06) + (check-equal? + (for/list ([h (in-list (take (vector->list (hash-ref top-dict 'CharStrings)) 100))]) + (index-item-offset h)) + '(83610 83750 83753 83755 83776 83778 83810 83858 83890 83951 84023 84046 84068 84096 84132 84169 84233 84270 84292 84322 84380 84411 84439 84478 84498 84547 84575 84679 84711 84751 84784 84823 84919 84956 84964 84978 85011 85013 85101 85188 85300 85302 85396 85398 85407 85422 85436 85451 85547 85561 85587 85647 85784 85790 85824 85864 85933 85935 85960 85970 85972 86003 86027 86091 86106 86161 86176 86228 86238 86253 86273 86288 86347 86363 86385 86401 86423 86463 86496 86511 86541 86568 86578 86594 86627 86651 86680 86731 86733 86766 86769 86861 86887 86900 86919 86986 87017 87061 87098 87108)) + ) \ No newline at end of file diff --git a/fontland/fontland/table/cff/cff-index.rkt b/fontland/fontland/table/cff/cff-index.rkt new file mode 100644 index 00000000..727bca61 --- /dev/null +++ b/fontland/fontland/table/cff/cff-index.rkt @@ -0,0 +1,117 @@ +#lang debug racket +(require racket/class racket/match xenomorph sugar/unstable/dict fontland/struct) +(provide CFFIndex) + +#| +approximates +https://github.com/mbutterick/fontkit/blob/master/src/cff/CFFIndex.js +|# + +(define CFFIndex% + (class x:base% + (super-new) + (init-field [(@type type) #f]) + + (define (getCFFVersion ctx) + (let loop ([ctx ctx]) + (cond + [(and ctx (hash? ctx) (not (hash-ref ctx 'hdrSize #f))) + (loop (hash-ref ctx 'x:parent))] + [(and ctx (hash-ref ctx 'x:version #f))] + [else -1]))) + + (define/augride (x:decode stream parent) + (match (decode (if (>= (getCFFVersion parent) 2) uint32be uint16be) stream) + [0 (vector)] + [count (define offSize (decode uint8 stream)) + (define offsetType (match offSize + [1 uint8] + [2 uint16be] + [3 uint24be] + [4 uint32be] + [_ (error (format "bad-offset-size-in-CFFIndex ~a" offSize))])) + (define startPos (+ (pos stream) (* (add1 count) offSize) -1)) + (for/fold ([vals null] + [start (send offsetType x:decode stream)] + #:result (begin0 (list->vector (reverse vals)) (pos stream (+ startPos start)))) + ([i (in-range count)]) + (define end (send offsetType x:decode stream)) + (define val + (cond + [@type + (define apos (pos stream)) + (pos stream (+ startPos start)) + (hash-set! parent 'length (- end start)) + (begin0 + (send @type x:decode stream parent) + (pos stream apos))] + [else + (index-item (+ startPos start) (- end start))])) + (values (cons val vals) end))])) + + (define/augride (x:size arr-arg parent) + (define arr (match arr-arg + [(? list? xs) (list->vector xs)] + [vec vec])) + (+ 2 + (cond + [(zero? (vector-length arr)) 0] + [else (define type (or @type (x:buffer))) + + ;; find maximum offset to determinine offset type + (define offset + (add1 (for/sum ([item (in-vector arr)]) + (send type x:size item parent)))) + + (define offset-type + (cond + [(<= offset #xff) uint8] + [(<= offset #xffff) uint16be] + [(<= offset #xffffff) uint24be] + [(<= offset #xffffffff) uint32be] + [else (error 'CFFIndex-size (format "bad offset: ~a" offset))])) + + (+ (* (send offset-type x:size) (add1 (vector-length arr))) offset)]))) + + (define/augride (x:encode arr-arg stream parent) + (define arr (match arr-arg + [(? list? xs) (list->vector xs)] + [vec vec])) + (send uint16be x:encode (vector-length arr) stream) + (cond + [(zero? (vector-length arr))] + [else + (define type (or @type (x:buffer))) + + ;; find maximum offset to detminine offset type + (define-values (sizes offset) + (for/fold ([sizes null] + [offset 1] + #:result (values (reverse sizes) offset)) + ([item (in-vector arr)]) + (define s (send type x:size item parent)) + (values (cons s sizes) (+ offset s)))) + + (define offsetType + (cond + [(<= offset #xff) uint8] + [(<= offset #xffff) uint16be] + [(<= offset #xffffff) uint24be] + [(<= offset #xffffffff) uint32be] + [else (error 'cff-index-encode-bad-offset!)])) + + ;; write offset size + (send uint8 x:encode (send offsetType x:size) stream) + + ;; write elements + (for/fold ([offset 1]) + ([size (in-list (cons 0 sizes))]) + (define next-offset (+ offset size)) + (send offsetType x:encode next-offset stream) + next-offset) + + (for ([item (in-vector arr)]) + (send type x:encode item stream parent))])))) + +(define (CFFIndex [type #f]) + (new CFFIndex% [type type])) \ No newline at end of file diff --git a/fontland/fontland/table/cff/cff-operand.rkt b/fontland/fontland/table/cff/cff-operand.rkt new file mode 100644 index 00000000..fefbaae0 --- /dev/null +++ b/fontland/fontland/table/cff/cff-operand.rkt @@ -0,0 +1,115 @@ +#lang debug racket/base +(require racket/class xenomorph "cff-struct.rkt") +(provide CFFOperand) + +#| +approximates +https://github.com/mbutterick/fontkit/blob/master/src/cff/CFFOperand.js +|# + +(define FLOAT_EOF #xf) + +(define FLOAT_LOOKUP (vector "0" "1" "2" "3" "4" "5" "6" "7" "8" "9" "." "E" "E-" "" "-")) + +(define FLOAT_ENCODE_LOOKUP + (hash "." 10 + "E" 11 + "E-" 12 + "-" 14)) + +(define CFFOperand% + (class x:base% + (super-new) + + (define/augment (x:decode stream _ value) + (cond + [(<= 32 value 246) (- value 139)] + [(<= 247 value 250) (+ (* (- value 247) 256) (read-byte stream) 108)] + [(<= 251 value 254) (- (* (- 251 value) 256) (read-byte stream) 108)] + [(= value 28) (decode int16be stream)] + [(= value 29) (decode int32be stream)] + [(= value 30) + (for/fold ([strs null] + [break? #false] + #:result (* (string->number (apply string-append (reverse strs))) 1.0)) + ([i (in-naturals)] + #:break break?) + (define b (read-byte stream)) + (define n1 (arithmetic-shift b -4)) + + (cond + [(= n1 FLOAT_EOF) (values strs 'break-now)] + [else + (let ([strs (cons (vector-ref FLOAT_LOOKUP n1) strs)]) + (define n2 (bitwise-and b 15)) + (cond + [(= n2 FLOAT_EOF) (values strs 'break-now)] + [else + (let ([strs (cons (vector-ref FLOAT_LOOKUP n2) strs)]) + (values strs #false))]))]))])) + + (define/augment (x:size value-arg _) + ;; if the value needs to be forced to the largest size (32 bit) + ;; e.g. for unknown pointers, set to 32768 + (define value (cond + [(Ptr? value-arg) (if (Ptr-forceLarge value-arg) 32768 (Ptr-val value-arg))] + [else value-arg])) + + (cond + [(not (integer? value)) ; floating point + (define str (number->string value)) + (add1 (ceiling (/ (add1 (string-length str)) 2)))] + [(<= -107 value 107) 1] + [(<= -1131 value 1131) 2] + [(<= -32768 value 32767) 3] + [else 5])) + + (define (string->inexact str) + (string->number str 10 'number-or-false 'decimal-as-inexact)) + + (define/augment (x:encode value-arg stream . _) + ;; if the value needs to be forced to the largest size (32 bit) + ;; e.g. for unknown pointers, save the old value and set to 32768 + (define value (if (Ptr? value-arg) (Ptr-val value-arg) value-arg)) + (define val (if value (string->inexact (format "~a" value)) 0)) + + (cond + [(and (Ptr? value-arg) (Ptr-forceLarge value-arg)) + (encode uint8 29 stream) + (encode int32be val stream)] + [(not (integer? val)) ;; floating point + (encode uint8 30 stream) + (define str (list->vector (regexp-match* #rx"." (number->string val)))) + (define n2 + (for/last ([i (in-range 0 (vector-length str) 2)]) + (define c1 (vector-ref str i)) + (define n1 (hash-ref FLOAT_ENCODE_LOOKUP c1 (string->number c1))) + (define n2 + (cond + [(= i (sub1 (vector-length str))) FLOAT_EOF] + [else + (define c2 (vector-ref str (add1 i))) + (hash-ref FLOAT_ENCODE_LOOKUP c2 (string->number c2))])) + (encode uint8 (bitwise-ior (arithmetic-shift n1 4) (bitwise-and n2 15)) stream) + n2)) + (unless (= n2 FLOAT_EOF) + (encode uint8 (arithmetic-shift FLOAT_EOF 4) stream))] + [(<= -107 value 107) + (encode uint8 (+ val 139) stream)] + [(<= 108 value 1131) + (let ([val (- val 108)]) + (encode uint8 (+ (arithmetic-shift val -8) 247) stream) + (encode uint8 (bitwise-and val #xff) stream))] + [(<= -1131 value -108) + (let ([val (- (+ val 108))]) + (encode uint8 (+ (arithmetic-shift val -8) 251) stream) + (encode uint8 (bitwise-and val #xff) stream))] + [(<= -32768 value 32767) + (encode uint8 28 stream) + (encode uint16be val stream)] + [else + (encode uint8 29 stream) + (encode uint32be val stream)])))) + + +(define CFFOperand (make-object CFFOperand%)) \ No newline at end of file diff --git a/fontland/fontland/table/cff/cff-pointer.rkt b/fontland/fontland/table/cff/cff-pointer.rkt new file mode 100644 index 00000000..524e8546 --- /dev/null +++ b/fontland/fontland/table/cff/cff-pointer.rkt @@ -0,0 +1,50 @@ +#lang debug racket/base +(require racket/class racket/list xenomorph/pointer xenomorph/base "cff-struct.rkt") +(provide CFFPointer CFFPointer%) + +#| +approximates +https://github.com/mbutterick/fontkit/blob/master/src/cff/CFFPointer.js +|# + + +(define (CFFPointer type + #:relative-to [relative-to 'global] + #:lazy [lazy #false]) + (x:pointer #:dest-type type + #:base-class CFFPointer% + #:relative-to relative-to + #:lazy lazy)) + +(define CFFPointer% + (class x:pointer% + (super-new) + + (inherit-field ptr-type) + + (define/override (x:decode stream parent operands) + (set! ptr-type (make-object + (class x:base% + (super-new) + (define/augment (x:decode . args) (first operands))))) + (super x:decode stream parent)) + + (define/override (x:encode value stream ctx) + (cond + [(not stream) + ;; compute the size (so ctx.pointerSize is correct) + (set! ptr-type (make-object + (class x:base% + (super-new) + (define/augment (x:size . args) 0)))) + (send this x:size value ctx) + (list (Ptr 0))] + [else + (define ptr #false) + (set! ptr-type (make-object + (class x:base% + (super-new) + (define/augment (x:encode val stream . _) (set! ptr val))))) + (super x:encode value stream ctx) + (list (Ptr ptr))])))) + diff --git a/fontland/fontland/table/cff/cff-private-dict.rkt b/fontland/fontland/table/cff/cff-private-dict.rkt new file mode 100644 index 00000000..b6b2d27d --- /dev/null +++ b/fontland/fontland/table/cff/cff-private-dict.rkt @@ -0,0 +1,37 @@ +#lang debug racket/base +(require racket/class racket/match xenomorph sugar/unstable/dict + "cff-dict.rkt" + "cff-index.rkt" + "cff-pointer.rkt") +(provide CFFPrivateDict) + +#| +approximates +https://github.com/mbutterick/fontkit/blob/master/src/cff/CFFPrivateDict.js +|# + + +(define CFFPrivateDict + (CFFDict + 'CFFPrivateDict + ;; key name type default + `((6 BlueValues delta #false) + (7 OtherBlues delta #false) + (8 FamilyBlues delta #false) + (9 FamilyOtherBlues delta #false) + ((12 9) BlueScale number 0.039625) + ((12 10) BlueShift number 7) + ((12 11) BlueFuzz number 1) + (10 StdHW number #false) + (11 StdVW number #false) + ((12 12) StemSnapH delta #false) + ((12 13) StemSnapV delta #false) + ((12 14) ForceBold boolean #false) + ((12 17) LanguageGroup number 0) + ((12 18) ExpansionFactor number 0.06) + ((12 19) initialRandomSeed number 0) + (20 defaultWidthX number 0) + (21 nominalWidthX number 0) + (22 vsindex number 0) + #;(23 blend boolean #false) + (19 Subrs ,(CFFPointer (CFFIndex) #:relative-to 'local) ,(vector))))) \ No newline at end of file diff --git a/fontland/fontland/table/cff/cff-standard-strings.rkt b/fontland/fontland/table/cff/cff-standard-strings.rkt new file mode 100644 index 00000000..00ea9d34 --- /dev/null +++ b/fontland/fontland/table/cff/cff-standard-strings.rkt @@ -0,0 +1,76 @@ +#lang racket/base +(provide (all-defined-out)) + +#| +approximates +https://github.com/mbutterick/fontkit/blob/master/src/cff/CFFStandardStrings.js +|# + +(define standard-strings + '#(".notdef" "space" "exclam" "quotedbl" "numbersign" "dollar" + "percent" "ampersand" "quoteright" "parenleft" "parenright" + "asterisk" "plus" "comma" "hyphen" "period" "slash" "zero" "one" + "two" "three" "four" "five" "six" "seven" "eight" "nine" "colon" + "semicolon" "less" "equal" "greater" "question" "at" "A" "B" "C" + "D" "E" "F" "G" "H" "I" "J" "K" "L" "M" "N" "O" "P" "Q" "R" + "S" "T" "U" "V" "W" "X" "Y" "Z" "bracketleft" "backslash" + "bracketright" "asciicircum" "underscore" "quoteleft" "a" "b" "c" + "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" + "s" "t" "u" "v" "w" "x" "y" "z" "braceleft" "bar" "braceright" + "asciitilde" "exclamdown" "cent" "sterling" "fraction" "yen" + "florin" "section" "currency" "quotesingle" "quotedblleft" + "guillemotleft" "guilsinglleft" "guilsinglright" "fi" "fl" "endash" + "dagger" "daggerdbl" "periodcentered" "paragraph" "bullet" + "quotesinglbase" "quotedblbase" "quotedblright" "guillemotright" + "ellipsis" "perthousand" "questiondown" "grave" "acute" "circumflex" + "tilde" "macron" "breve" "dotaccent" "dieresis" "ring" "cedilla" + "hungarumlaut" "ogonek" "caron" "emdash" "AE" "ordfeminine" "Lslash" + "Oslash" "OE" "ordmasculine" "ae" "dotlessi" "lslash" "oslash" "oe" + "germandbls" "onesuperior" "logicalnot" "mu" "trademark" "Eth" + "onehalf" "plusminus" "Thorn" "onequarter" "divide" "brokenbar" + "degree" "thorn" "threequarters" "twosuperior" "registered" "minus" + "eth" "multiply" "threesuperior" "copyright" "Aacute" "Acircumflex" + "Adieresis" "Agrave" "Aring" "Atilde" "Ccedilla" "Eacute" + "Ecircumflex" "Edieresis" "Egrave" "Iacute" "Icircumflex" "Idieresis" + "Igrave" "Ntilde" "Oacute" "Ocircumflex" "Odieresis" "Ograve" + "Otilde" "Scaron" "Uacute" "Ucircumflex" "Udieresis" "Ugrave" + "Yacute" "Ydieresis" "Zcaron" "aacute" "acircumflex" "adieresis" + "agrave" "aring" "atilde" "ccedilla" "eacute" "ecircumflex" + "edieresis" "egrave" "iacute" "icircumflex" "idieresis" "igrave" + "ntilde" "oacute" "ocircumflex" "odieresis" "ograve" "otilde" + "scaron" "uacute" "ucircumflex" "udieresis" "ugrave" "yacute" + "ydieresis" "zcaron" "exclamsmall" "Hungarumlautsmall" + "dollaroldstyle" "dollarsuperior" "ampersandsmall" "Acutesmall" + "parenleftsuperior" "parenrightsuperior" "twodotenleader" + "onedotenleader" "zerooldstyle" "oneoldstyle" "twooldstyle" + "threeoldstyle" "fouroldstyle" "fiveoldstyle" "sixoldstyle" + "sevenoldstyle" "eightoldstyle" "nineoldstyle" "commasuperior" + "threequartersemdash" "periodsuperior" "questionsmall" "asuperior" + "bsuperior" "centsuperior" "dsuperior" "esuperior" "isuperior" + "lsuperior" "msuperior" "nsuperior" "osuperior" "rsuperior" + "ssuperior" "tsuperior" "ff" "ffi" "ffl" "parenleftinferior" + "parenrightinferior" "Circumflexsmall" "hyphensuperior" "Gravesmall" + "Asmall" "Bsmall" "Csmall" "Dsmall" "Esmall" "Fsmall" "Gsmall" + "Hsmall" "Ismall" "Jsmall" "Ksmall" "Lsmall" "Msmall" "Nsmall" + "Osmall" "Psmall" "Qsmall" "Rsmall" "Ssmall" "Tsmall" "Usmall" + "Vsmall" "Wsmall" "Xsmall" "Ysmall" "Zsmall" "colonmonetary" + "onefitted" "rupiah" "Tildesmall" "exclamdownsmall" "centoldstyle" + "Lslashsmall" "Scaronsmall" "Zcaronsmall" "Dieresissmall" "Brevesmall" + "Caronsmall" "Dotaccentsmall" "Macronsmall" "figuredash" + "hypheninferior" "Ogoneksmall" "Ringsmall" "Cedillasmall" + "questiondownsmall" "oneeighth" "threeeighths" "fiveeighths" + "seveneighths" "onethird" "twothirds" "zerosuperior" "foursuperior" + "fivesuperior" "sixsuperior" "sevensuperior" "eightsuperior" + "ninesuperior" "zeroinferior" "oneinferior" "twoinferior" + "threeinferior" "fourinferior" "fiveinferior" "sixinferior" + "seveninferior" "eightinferior" "nineinferior" "centinferior" + "dollarinferior" "periodinferior" "commainferior" "Agravesmall" + "Aacutesmall" "Acircumflexsmall" "Atildesmall" "Adieresissmall" + "Aringsmall" "AEsmall" "Ccedillasmall" "Egravesmall" "Eacutesmall" + "Ecircumflexsmall" "Edieresissmall" "Igravesmall" "Iacutesmall" + "Icircumflexsmall" "Idieresissmall" "Ethsmall" "Ntildesmall" + "Ogravesmall" "Oacutesmall" "Ocircumflexsmall" "Otildesmall" + "Odieresissmall" "OEsmall" "Oslashsmall" "Ugravesmall" "Uacutesmall" + "Ucircumflexsmall" "Udieresissmall" "Yacutesmall" "Thornsmall" + "Ydieresissmall" "001.000" "001.001" "001.002" "001.003" "Black" + "Bold" "Book" "Light" "Medium" "Regular" "Roman" "Semibold")) \ No newline at end of file diff --git a/fontland/fontland/table/cff/cff-struct.rkt b/fontland/fontland/table/cff/cff-struct.rkt new file mode 100644 index 00000000..dd11d3ac --- /dev/null +++ b/fontland/fontland/table/cff/cff-struct.rkt @@ -0,0 +1,3 @@ +#lang racket/base +(provide (all-defined-out)) +(struct Ptr (val [forceLarge #:auto]) #:transparent #:mutable #:auto-value #true) \ No newline at end of file diff --git a/fontland/fontland/table/cff/cff-top.rkt b/fontland/fontland/table/cff/cff-top.rkt new file mode 100644 index 00000000..a2706f9e --- /dev/null +++ b/fontland/fontland/table/cff/cff-top.rkt @@ -0,0 +1,222 @@ +#lang debug racket/base +(require xenomorph + racket/list + racket/vector + racket/match + sugar/unstable/dict + racket/class + racket/dict + "cff-index.rkt" + "cff-dict.rkt" + "cff-charsets.rkt" + "cff-pointer.rkt" + "cff-encodings.rkt" + "cff-private-dict.rkt") +(provide CFFTop) + +#| +approximates +https://github.com/mbutterick/fontkit/blob/master/src/cff/CFFTop.js +|# + +(define PredefinedOp% + (class x:base% + (super-new) + (init-field [(@predefinedOps predefinedOps)] + [(@type type) #f]) + (field [op-vec (list->vector @predefinedOps)]) + + (define/override (pre-encode val) + ;; because fontkit depends on overloading 'version key, and we don't + (let ([val (make-hasheq val)]) + (hash-set! val 'x:version (hash-ref val 'version)) + val)) + + (define/augment (x:decode stream parent operands) + (define idx (car operands)) + (if (< idx (vector-length op-vec)) + (vector-ref op-vec idx) + (decode @type stream #:parent parent operands))) + + (define/augment (x:size value ctx) + (error 'predefined-op-size-not-finished)) + + (define/augment (x:encode value stream ctx) + (or (vector-member value op-vec) + (send @type x:encode value stream ctx))))) + +(define (PredefinedOp predefinedOps type) (make-object PredefinedOp% predefinedOps type)) + +(define CFFEncodingVersion + (x:int #:size 1 + #:signed #false + #:post-decode (λ (res) (bitwise-and res #x7f)))) + +(define Range1 + (x:struct + 'first uint16be + 'nLeft uint8)) + +(define Range2 + (x:struct + 'first uint16be + 'nLeft uint16be)) + +(define CFFCustomEncoding + (x:versioned-struct + CFFEncodingVersion + (dictify + 0 (dictify 'nCodes uint8 + 'codes (x:list #:type uint8 #:length (λ (p) (hash-ref p 'nCodes)))) + 1 (dictify 'nRanges uint8 + 'ranges (x:list #:type Range1 #:length (λ (p) (hash-ref p 'nRanges))))))) + +(define CFFEncoding (PredefinedOp (list StandardEncoding ExpertEncoding) + (CFFPointer CFFCustomEncoding #:lazy #true))) + +;; Decodes an array of ranges until the total +;; length is equal to the provided length. + +(define RangeArray% + (class x:list% + (super-new) + (inherit-field [@len len] [@type type]) + (define/override (x:decode stream parent) + (define length (resolve-length @len stream parent)) + (for/fold ([res null] + [count 0] + #:result (reverse res)) + ([i (in-naturals)] + #:break (not (< count length))) + (define range (decode @type stream parent)) + (hash-set! range 'offset count) + (values (cons range res) (+ count (hash-ref range 'nLeft) 1)))))) + +(define (RangeArray . args) (apply x:list #:base-class RangeArray% args)) + +(define (base-tproc t) (length (hash-ref (hash-ref t 'parent) 'CharStrings))) + +(define CFFCustomCharset + (x:versioned-struct + uint8 + (let ([tproc (λ (t) (sub1 (base-tproc t)))]) + (dictify + 0 (dictify 'glyphs (x:list uint16be tproc)) + 1 (dictify 'ranges (RangeArray Range1 tproc)) + 2 (dictify 'ranges (RangeArray Range2 tproc)))))) + +(define CFFCharset (PredefinedOp + (list ISOAdobeCharset ExpertCharset ExpertSubsetCharset) + (CFFPointer CFFCustomCharset #:lazy #true))) + +(define FDRange3 + (x:struct 'first uint16be + 'fd uint8)) + +(define FDRange4 + (x:struct 'first uint32be + 'fd uint16be)) + +(define FDSelect + (x:versioned-struct + uint8 + #:version-key 'version + (dictify + 0 (dictify 'fds (x:list uint8 base-tproc)) + 3 (dictify 'nRanges uint16be + 'ranges (x:list #:type FDRange3 #:length (λ (p) (hash-ref p 'nRanges))) + 'sentinel uint16be) + 4 (dictify 'nRanges uint32be + 'ranges (x:list #:type FDRange4 #:length (λ (p) (hash-ref p 'nRanges))) + 'sentinel uint32be)))) + +(define ptr (CFFPointer CFFPrivateDict)) + +(define CFFPrivateOp% + (class x:base% + (super-new) + + (define/augment (x:decode stream parent operands) + (match operands + [(list op1 op2) + (hash-set! parent 'length op1) + (send ptr x:decode stream parent (list op2))])) + + (define/augment (x:size dict ctx) + (list (send CFFPrivateDict x:size dict ctx #false) + (car (send ptr x:size dict ctx)))) + + (define/augment (x:encode dict stream ctx) + (list (send CFFPrivateDict x:size dict ctx #false) + (car (send ptr x:encode dict stream ctx)))))) + +(define (CFFPrivateOp) + (make-object CFFPrivateOp%)) + +(define FontDict + (CFFDict + 'FontDict + ;; key name type(s) default + `((18 Private ,(CFFPrivateOp) #false) + ((12 38) FontName sid #false)))) + +(define CFFTopDict + (CFFDict + 'CFFTopDict + ;; key name type(s) default + `(((12 30) ROS (sid sid number) #false) + (0 version sid #false) + (1 Notice sid #false) + ((12 0) Copyright sid #false) + (2 FullName sid #false) + (3 FamilyName sid #false) + (4 Weight sid #false) + ((12 1) isFixedPitch boolean #false) + ((12 2) ItalicAngle number 0) + ((12 3) UnderlinePosition number -100) + ((12 4) UnderlineThickness number 50) + ((12 5) PaintType number 0) + ((12 6) CharstringType number 2) + ((12 7) FontMatrix array (0.001 0 0 0.001 0 0)) + (13 UniqueID number #false) + (5 FontBBox array (0 0 0 0)) + ((12 8) StrokeWidth number 0) + (14 XUID array #false) + (15 charset ,CFFCharset ,ISOAdobeCharset) + (16 Encoding ,CFFEncoding ,StandardEncoding) + (17 CharStrings ,(CFFPointer (CFFIndex)) #false) + (18 Private ,(CFFPrivateOp) #false) + ((12 20) SyntheticBase number #false) + ((12 21) PostScript sid #false) + ((12 22) BaseFontName sid #false) + ((12 23) BaseFontBlend delta #false) + + ;; CID font specific + ((12 31) CIDFontVersion number 0) + ((12 32) CIDFontRevision number 0) + ((12 33) CIDFontType number 0) + ((12 34) CIDCount number 8720) + ((12 35) UIDBase number #false) + ((12 37) FDSelect ,(CFFPointer FDSelect) #false) + ((12 36) FDArray ,(CFFPointer (CFFIndex FontDict)) #false) + ((12 38) FontName sid #false)))) + +(define CFFTop + (x:versioned-struct + #:version-key 'version + fixed16be + (dictify + 1 (dictify 'hdrSize uint8 + 'offSize uint8 + 'nameIndex (CFFIndex (x:string #:length (λ (p) (hash-ref p 'length)))) + 'topDictIndex (CFFIndex CFFTopDict) + 'stringIndex (CFFIndex (x:string #:length (λ (p) (hash-ref p 'length)))) + 'globalSubrIndex (CFFIndex)) + + #| +2 (dictify 'hdrSize uint8 + 'length uint16be + 'topDict CFF2TopDict + 'globalSubrIndex (CFFIndex)) +|# + ))) \ No newline at end of file diff --git a/fontland/fontland/table/cvt_.rkt b/fontland/fontland/table/cvt_.rkt new file mode 100644 index 00000000..85ae4dcc --- /dev/null +++ b/fontland/fontland/table/cvt_.rkt @@ -0,0 +1,26 @@ +#lang racket/base +(require xenomorph) +(provide cvt_) + +#| +approximates +https://github.com/mbutterick/fontkit/blob/master/src/tables/cvt.js +|# + +(define cvt_ (x:struct 'controlValues (x:array #:type int16be))) + +(module+ test + (require rackunit racket/serialize "../helper.rkt") + (define ip (open-input-file charter-path)) + (define dir (deserialize (read (open-input-file charter-directory-path)))) + (define offset (hash-ref (hash-ref (hash-ref dir 'tables) 'cvt_) 'offset)) + (define len (hash-ref (hash-ref (hash-ref dir 'tables) 'cvt_) 'length)) + (check-equal? offset 4592) + (check-equal? len 26) + (file-position ip 0) + (define table-bytes #"\0\24\0+\0S\0\0\0\20\377&\0\0\1\341\0\v\2\237\0\22\2\340\0\b") + (check-equal? table-bytes (peek-bytes len offset ip)) + (define ds (open-input-bytes (peek-bytes len offset ip))) + (define cvt-array '(20 43 83 0 16 -218 0 481 11 671 18 736 8)) + (check-equal? (hash-ref (decode cvt_ ds) 'controlValues) cvt-array) + (check-equal? (encode cvt_ (make-hash (list (cons 'controlValues cvt-array))) #f) table-bytes)) diff --git a/fontland/fontland/table/fpgm.rkt b/fontland/fontland/table/fpgm.rkt new file mode 100644 index 00000000..f4b22967 --- /dev/null +++ b/fontland/fontland/table/fpgm.rkt @@ -0,0 +1,25 @@ +#lang racket/base +(require xenomorph) +(provide fpgm) +#| +approximates +https://github.com/mbutterick/fontkit/blob/master/src/tables/fpgm.js +|# + +;; A list of instructions that are executed once when a font is first used. +;; These instructions are known as the font program. The main use of this table +;; is for the definition of functions that are used in many different glyph programs. + +(define fpgm (x:struct 'instructions (x:array #:type uint8))) + +(module+ test + (require rackunit racket/serialize "../helper.rkt") + (define ip (open-input-file charter-path)) + (define dir (deserialize (read (open-input-file charter-directory-path)))) + (define offset (hash-ref (hash-ref (hash-ref dir 'tables) 'fpgm) 'offset)) + (define len (hash-ref (hash-ref (hash-ref dir 'tables) 'fpgm) 'length)) + (check-equal? offset 4140) + (check-equal? len 371) + (check-equal? (pos ip 0) 0) + (check-equal? (hash-ref (decode fpgm (peek-bytes len offset ip)) 'instructions) '(184 0 0 44 75 184 0 9 80 88 177 1 1 142 89 184 1 255 133 184 0 68 29 185 0 9 0 3 95 94 45 184 0 1 44 32 32 69 105 68 176 1 96 45 184 0 2 44 184 0 1 42 33 45 184 0 3 44 32 70 176 3 37 70 82 88 35 89 32 138 32 138 73 100 138 32 70 32 104 97 100 176 4 37 70 32 104 97 100 82 88 35 101 138 89 47 32 176 0 83 88 105 32 176 0 84 88 33 176 64 89 27 105 32 176 0 84 88 33 176 64 101 89 89 58 45 184 0 4 44 32 70 176 4 37 70 82 88 35 138 89 32 70 32 106 97 100 176 4 37 70 32 106 97 100 82 88 35 138 89 47 253 45 184 0 5 44 75 32 176 3 38 80 88 81 88 176 128 68 27 176 64 68 89 27 33 33 32 69 176 192 80 88 176 192 68 27 33 89 89 45 184 0 6 44 32 32 69 105 68 176 1 96 32 32 69 125 105 24 68 176 1 96 45 184 0 7 44 184 0 6 42 45 184 0 8 44 75 32 176 3 38 83 88 176 64 27 176 0 89 138 138 32 176 3 38 83 88 35 33 176 128 138 138 27 138 35 89 32 176 3 38 83 88 35 33 184 0 192 138 138 27 138 35 89 32 176 3 38 83 88 35 33 184 1 0 138 138 27 138 35 89 32 176 3 38 83 88 35 33 184 1 64 138 138 27 138 35 89 32 184 0 3 38 83 88 176 3 37 69 184 1 128 80 88 35 33 184 1 128 35 33 27 176 3 37 69 35 33 35 33 89 27 33 89 68 45 184 0 9 44 75 83 88 69 68 27 33 33 89 45))) + diff --git a/fontland/fontland/table/glyf.rkt b/fontland/fontland/table/glyf.rkt new file mode 100644 index 00000000..68be4b04 --- /dev/null +++ b/fontland/fontland/table/glyf.rkt @@ -0,0 +1,20 @@ +#lang racket/base +(require xenomorph) +(provide glyf) +#| +approximates +https://github.com/mbutterick/fontkit/blob/master/src/tables/glyf.js +|# + +(define glyf (x:array #:type (x:buffer))) + +(module+ test + (require rackunit racket/serialize "../helper.rkt") + (define ip (open-input-file charter-path)) + (define dir (deserialize (read (open-input-file charter-directory-path)))) + (define offset (hash-ref (hash-ref (hash-ref dir 'tables) 'glyf) 'offset)) + (define len (hash-ref (hash-ref (hash-ref dir 'tables) 'glyf) 'length)) + (check-equal? offset 4620) + (check-equal? len 34072) + (file-position ip 0) + (define table-bytes (peek-bytes len offset ip))) diff --git a/fontland/fontland/table/head.rkt b/fontland/fontland/table/head.rkt new file mode 100644 index 00000000..d61b17b4 --- /dev/null +++ b/fontland/fontland/table/head.rkt @@ -0,0 +1,71 @@ +#lang debug racket/base +(require xenomorph + racket/date) +(provide head) + +#| +approximates +https://github.com/mbutterick/fontkit/blob/master/src/tables/head.js +|# + +; `name` table dates are seconds since 1/1/1904 (Mac file convention) +; whereas Racket / posix tracks since 1/1/1970 +; so we adjust with the difference in seconds +(define mac-to-posix-delta 2082844800) + +(define hfs-seconds (x:int #:size 8 + #:signed #f + #:endian 'be + #:post-decode (λ (int) (seconds->date (- int mac-to-posix-delta) #f)) + #:pre-encode (λ (dt) (+ (date->seconds dt #f) mac-to-posix-delta)))) + +(define head (x:struct + 'version int32be ;; 0x00010000 (version 1.0) + 'revision int32be ;; set by font manufacturer + 'checkSumAdjustment uint32be + 'magicNumber uint32be ;; set to 0x5F0F3CF5 + 'flags uint16be + 'unitsPerEm uint16be ;; range from 64 to 16384 + 'created hfs-seconds + 'modified hfs-seconds + 'xMin int16be ;; for all glyph bounding boxes + 'yMin int16be ;; for all glyph bounding boxes + 'xMax int16be ;; for all glyph bounding boxes + 'yMax int16be ;; for all glyph bounding boxes + 'macStyle (x:bitfield #:type uint16be + #:flags '(bold italic underline outline shadow condensed extended)) + 'lowestRecPPEM uint16be ;; smallest readable size in pixels + 'fontDirectionHint int16be + 'indexToLocFormat int16be ;; 0 for short offsets 1 for long + 'glyphDataFormat int16be ;; 0 for current format + )) + + +(module+ test + (require rackunit "../helper.rkt" + racket/serialize) + (define ip (open-input-file charter-italic-path)) + (define dir (deserialize (read (open-input-file charter-italic-directory-path)))) + (define offset (hash-ref (hash-ref (hash-ref dir 'tables) 'head) 'offset)) + (define length (hash-ref (hash-ref (hash-ref dir 'tables) 'head) 'length)) + (check-equal? offset 236) + (check-equal? length 54) + (define table-bytes #"\0\1\0\0\0\2\0\0.\252t<_\17<\365\0\t\3\350\0\0\0\0\316\3\301\261\0\0\0\0\316\3\304\364\377\36\377\24\4\226\3\324\0\2\0\t\0\2\0\0\0\0") + (file-position ip 0) + (check-equal? (peek-bytes length offset ip) table-bytes) + (define table-data (decode head table-bytes)) + (check-equal? (hash-ref table-data 'unitsPerEm) 1000) + (check-equal? (hash-ref table-data 'yMin) -236) + (check-equal? (hash-ref table-data 'yMax) 980) + (check-equal? (hash-ref table-data 'xMax) 1174) + (check-equal? (hash-ref table-data 'xMin) -226) + (check-equal? (hash-ref table-data 'macStyle) (make-hash '((shadow . #f) + (extended . #f) + (condensed . #f) + (underline . #f) + (outline . #f) + (bold . #f) + (italic . #t)))) + (check-equal? (hash-ref table-data 'magicNumber) #x5F0F3CF5) + (check-equal? (hash-ref table-data 'indexToLocFormat) 0) ; used in loca table + (check-equal? (encode head table-data #f) table-bytes)) \ No newline at end of file diff --git a/fontland/fontland/table/hhea.rkt b/fontland/fontland/table/hhea.rkt new file mode 100644 index 00000000..bdc156c4 --- /dev/null +++ b/fontland/fontland/table/hhea.rkt @@ -0,0 +1,39 @@ +#lang racket/base +(require xenomorph sugar/unstable/dict) +(provide hhea) + +(define hhea (x:struct + (dictify + 'version int32be + 'ascent int16be ;; Distance from baseline of highest ascender + 'descent int16be ;; Distance from baseline of lowest descender + 'lineGap int16be ;; Typographic line gap + 'advanceWidthMax uint16be ;; Maximum advance width value in 'hmtx' table + 'minLeftSideBearing int16be ;; Maximum advance width value in 'hmtx' table + 'minRightSideBearing int16be ;; Minimum right sidebearing value + 'xMaxExtent int16be + 'caretSlopeRise int16be ;; Used to calculate the slope of the cursor (rise/run); 1 for vertical + 'caretSlopeRun int16be ;; 0 for vertical + 'caretOffset int16be ;; Set to 0 for non-slanted fonts + 'reserved (x:array #:type int16be #:length 4) + 'metricDataFormat int16be ;; 0 for current format + 'numberOfMetrics uint16be ;; Number of advance widths in 'hmtx' table + ))) + +(module+ test + (require rackunit "../helper.rkt" + racket/serialize) + (define ip (open-input-file charter-path)) + (define dir (deserialize (read (open-input-file charter-directory-path)))) + (define offset (hash-ref (hash-ref (hash-ref dir 'tables) 'hhea) 'offset)) + (define length (hash-ref (hash-ref (hash-ref dir 'tables) 'hhea) 'length)) + (check-equal? offset 292) + (check-equal? length 36) + (define table-bytes #"\0\1\0\0\3\324\377\22\0\0\4\311\377_\377`\4\251\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\345") + (file-position ip 0) + (check-equal? (peek-bytes length offset ip) table-bytes) + (define table-data (decode hhea table-bytes)) + (check-equal? (hash-ref table-data 'ascent) 980) + (check-equal? (hash-ref table-data 'descent) -238) + (check-equal? (hash-ref table-data 'numberOfMetrics) 229)) + diff --git a/fontland/fontland/table/hmtx.rkt b/fontland/fontland/table/hmtx.rkt new file mode 100644 index 00000000..9f1d9d80 --- /dev/null +++ b/fontland/fontland/table/hmtx.rkt @@ -0,0 +1,35 @@ +#lang racket/base +(require xenomorph) +(provide (all-defined-out)) + +#| +approximates +https://github.com/mbutterick/fontkit/blob/master/src/tables/hmtx.js +|# + +(define hmtx-entry (x:struct 'advance uint16be 'bearing int16be)) + +(define hmtx (x:struct 'metrics (x:lazy-array #:type hmtx-entry + #:length (λ (arr) (hash-ref (hash-ref (hash-ref arr 'parent) 'hhea) 'numberOfMetrics))) + 'bearings (x:lazy-array #:type int16be + #:length (λ (arr) (- (hash-ref (hash-ref (hash-ref arr 'parent) 'maxp) 'numGlyphs) + (hash-ref (hash-ref (hash-ref arr 'parent) 'hhea) 'numberOfMetrics)))))) + +(module+ test + (require rackunit racket/serialize racket/stream racket/class "../helper.rkt") + ;; same as hmtx but doesn't require resolution of function to get length + (define hmtx-test (x:struct + 'metrics (x:lazy-array hmtx-entry (λ (t) 229)) + 'bearing (x:lazy-array int16be (λ (t) 0)))) + (define ip (open-input-file charter-path)) + (define dir (deserialize (read (open-input-file charter-directory-path)))) + (define hmtx-offset (hash-ref (hash-ref (hash-ref dir 'tables) 'hmtx) 'offset)) + (define hmtx-length (hash-ref (hash-ref (hash-ref dir 'tables) 'hmtx) 'length)) + (check-equal? hmtx-offset 456) + (check-equal? hmtx-length 916) + (define hmtx-bytes (peek-bytes hmtx-length hmtx-offset ip)) + (define hmtx-data (decode hmtx-test hmtx-bytes)) + (check-equal? (send hmtx-test x:size) (* 229 (send hmtx-entry x:size))) + (define H-gid 41) (define OE-gid 142) + (check-equal? (stream-ref (hash-ref hmtx-data 'metrics) H-gid) (make-hasheq '((bearing . 33) (advance . 738)))) + (check-equal? (stream-ref (hash-ref hmtx-data 'metrics) OE-gid) (make-hasheq '((bearing . 43) (advance . 993))))) \ No newline at end of file diff --git a/fontland/fontland/table/loca.rkt b/fontland/fontland/table/loca.rkt new file mode 100644 index 00000000..da126409 --- /dev/null +++ b/fontland/fontland/table/loca.rkt @@ -0,0 +1,66 @@ +#lang debug racket/base +(require xenomorph + sugar/unstable/dict + racket/list + racket/promise + "../struct.rkt") +(provide loca loca-pre-encode loca-post-decode) + +(define 16bit-style 0) +(define 32bit-style 1) +(define max-16-bit-value #xffff) + +#| +approximates +https://github.com/mbutterick/fontkit/blob/master/src/tables/loca.js +|# + +(define (loca-pre-encode val) + (unless (hash-has-key? val x:version-key) + (hash-set! val x:version-key (if (> (last (hash-ref val 'offsets)) max-16-bit-value) + 32bit-style + 16bit-style)) + (when (= 16bit-style (hash-ref val x:version-key)) + (hash-update! val 'offsets (λ (offsets) (map (λ (x) (/ x 2)) offsets))))) + val) + +(define (loca-post-decode val) + (when (= 16bit-style (hash-ref val x:version-key)) + ;; in a 16bits-style loca table, actual 32bit offset values are divided by 2 (to fit into 16 bits) + ;; so we re-inflate them. + (hash-update! val 'offsets (λ (offsets) (map (λ (x) (* 2 x)) offsets)))) + val) + +(define loca (x:versioned-struct + #:pre-encode loca-pre-encode + #:post-decode loca-post-decode + ;; todo: address ugliness to cross-ref head table from ttffont + (λ (o) (hash-ref (force (ttf-font-get-head-table-proc o)) 'indexToLocFormat)) + (dictify + 0 (dictify 'offsets (x:array #:type uint16be)) + 1 (dictify 'offsets (x:array #:type uint32be))))) + +(define loca-v0 (x:versioned-struct + #:pre-encode loca-pre-encode + #:post-decode loca-post-decode + 0 + (dictify + 0 (dictify 'offsets (x:array #:type uint16be)) + 1 (dictify 'offsets (x:array #:type uint32be))))) + +(module+ test + (require rackunit racket/serialize "../helper.rkt") + (check-equal? + (encode loca (mhasheq x:version-key 0 'offsets '(0 76 156)) #f) #"\0\0\0L\0\234") + (check-equal? + (encode loca (mhasheq x:version-key 1 'offsets '(0 76 156)) #f) #"\0\0\0\0\0\0\0L\0\0\0\234") + (define ip (open-input-file charter-path)) + (define dir (deserialize (read (open-input-file charter-directory-path)))) + (define offset (hash-ref (hash-ref (hash-ref dir 'tables) 'loca) 'offset)) + (define len (hash-ref (hash-ref (hash-ref dir 'tables) 'loca) 'length)) + (check-equal? offset 38692) + (check-equal? len 460) + (define offset-bytes (peek-bytes len offset ip)) + (define offsets (map (λ (x) (* 2 x)) (decode (x:array uint16be) offset-bytes))) + (check-equal? (length offsets) 230) + (check-equal? offsets '(0 0 0 136 296 500 864 1168 1548 1628 1716 1804 1944 2048 2128 2176 2256 2312 2500 2596 2788 3052 3168 3396 3624 3732 4056 4268 4424 4564 4640 4728 4804 5012 5384 5532 5808 6012 6212 6456 6672 6916 7204 7336 7496 7740 7892 8180 8432 8648 8892 9160 9496 9764 9936 10160 10312 10536 10780 10992 11148 11216 11272 11340 11404 11444 11524 11820 12044 12216 12488 12728 12932 13324 13584 13748 13924 14128 14232 14592 14852 15044 15336 15588 15776 16020 16164 16368 16520 16744 16984 17164 17320 17532 17576 17788 17896 18036 18284 18552 18616 18988 19228 19512 19712 19796 19976 20096 20160 20224 20536 20836 20876 21000 21200 21268 21368 21452 21532 21720 21908 22036 22244 22664 22872 22932 22992 23088 23220 23268 23372 23440 23600 23752 23868 23988 24084 24184 24224 24548 24788 25012 25292 25716 25884 26292 26396 26540 26796 27172 27488 27512 27536 27560 27584 27912 27936 27960 27984 28008 28032 28056 28080 28104 28128 28152 28176 28200 28224 28248 28272 28296 28320 28344 28368 28392 28416 28440 28464 28488 28512 28536 28560 28968 28992 29016 29040 29064 29088 29112 29136 29160 29184 29208 29232 29256 29280 29304 29328 29352 29376 29400 29424 29448 29472 29496 29520 29824 30164 30220 30652 30700 30956 31224 31248 31332 31488 31636 31916 32104 32176 32484 32744 32832 32956 33248 33664 33884 34048 34072))) diff --git a/fontland/fontland/table/maxp.rkt b/fontland/fontland/table/maxp.rkt new file mode 100644 index 00000000..15eaf861 --- /dev/null +++ b/fontland/fontland/table/maxp.rkt @@ -0,0 +1,36 @@ +#lang racket/base +(require xenomorph) +(provide maxp) + +(define maxp (x:struct + 'version int32be + 'numGlyphs uint16be ;; The number of glyphs in the font + 'maxPoints uint16be ;; Maximum points in a non-composite glyph + 'maxContours uint16be ;; Maximum contours in a non-composite glyph + 'maxComponentPoints uint16be ;; Maximum points in a composite glyph + 'maxComponentContours uint16be ;; Maximum contours in a composite glyph + 'maxZones uint16be ;; 1 if instructions do not use the twilight zone, 2 otherwise + 'maxTwilightPoints uint16be ;; Maximum points used in Z0 + 'maxStorage uint16be ;; Number of Storage Area locations + 'maxFunctionDefs uint16be ;; Number of FDEFs + 'maxInstructionDefs uint16be ;; Number of IDEFs + 'maxStackElements uint16be ;; Maximum stack depth + 'maxSizeOfInstructions uint16be ;; Maximum byte count for glyph instructions + 'maxComponentElements uint16be ;; Maximum number of components referenced at “top level” for any composite glyph + 'maxComponentDepth uint16be ;; Maximum levels of recursion; 1 for simple components + )) + +(module+ test + (require rackunit racket/serialize "../helper.rkt") + (define ip (open-input-file charter-path)) + (define dir (deserialize (read (open-input-file charter-directory-path)))) + (define maxp-offset (hash-ref (hash-ref (hash-ref dir 'tables) 'maxp) 'offset)) + (define maxp-length (hash-ref (hash-ref (hash-ref dir 'tables) 'maxp) 'length)) + (check-equal? maxp-offset 328) + (check-equal? maxp-length 32) + (define maxp-bytes #"\0\1\0\0\0\345\0f\0\a\0O\0\4\0\1\0\0\0\0\0\n\0\0\2\0\1s\0\2\0\1") + (file-position ip 0) + (check-equal? (peek-bytes maxp-length maxp-offset ip) maxp-bytes) + (define maxp-data (decode maxp maxp-bytes)) + (check-equal? (hash-ref maxp-data 'numGlyphs) 229) + (check-equal? (hash-ref maxp-data 'version) 65536)) \ No newline at end of file diff --git a/fontland/fontland/table/post.rkt b/fontland/fontland/table/post.rkt new file mode 100644 index 00000000..4463af2f --- /dev/null +++ b/fontland/fontland/table/post.rkt @@ -0,0 +1,40 @@ +#lang racket/base +(require xenomorph + sugar/unstable/dict) +(provide post) + +#| +approximates +https://github.com/mbutterick/fontkit/blob/master/src/tables/post.js +|# + +(define post (x:versioned-struct + fixed32be + (dictify + 'header (dictify 'italicAngle fixed32be ;; Italic angle in counter-clockwise degrees from the vertical. + 'underlinePosition int16be ;; Suggested distance of the top of the underline from the baseline + 'underlineThickness int16be ;; Suggested values for the underline thickness + 'isFixedPitch uint32be ;; Whether the font is monospaced + 'minMemType42 uint32be ;; Minimum memory usage when a TrueType font is downloaded as a Type 42 font + 'maxMemType42 uint32be ;; Maximum memory usage when a TrueType font is downloaded as a Type 42 font + 'minMemType1 uint32be ;; Minimum memory usage when a TrueType font is downloaded as a Type 1 font + 'maxMemType1 uint32be) ;; Maximum memory usage when a TrueType font is downloaded as a Type 1 font + + 1 null + 2 (dictify 'numberOfGlyphs uint16be + 'glyphNameIndex (x:array #:type uint16be #:length (λ (p) (hash-ref p 'numberOfGlyphs))) + 'names (x:array (x:string #:length uint8)) + ) + 2.5 (dictify 'numberOfGlyphs uint16be + 'offsets (x:array #:type uint8)) + 3 null + 4 (dictify 'map (x:array #:type uint32be #:length (λ (t) (hash-ref (hash-ref (hash-ref t 'parent) 'maxp) 'numGlyphs))))))) + +(module+ test + (require rackunit racket/serialize racket/class "../helper.rkt") + (define ip (open-input-file charter-path)) + (define dir (deserialize (read (open-input-file charter-directory-path)))) + (define offset (hash-ref (hash-ref (hash-ref dir 'tables) 'post) 'offset)) + (define len (hash-ref (hash-ref (hash-ref dir 'tables) 'post) 'length)) + (check-equal? offset 41520) + (check-equal? len 514)) \ No newline at end of file diff --git a/fontland/fontland/table/prep.rkt b/fontland/fontland/table/prep.rkt new file mode 100644 index 00000000..f032422f --- /dev/null +++ b/fontland/fontland/table/prep.rkt @@ -0,0 +1,24 @@ +#lang racket/base +(require sugar/unstable/dict xenomorph) +(provide prep) + +#| +approximates +https://github.com/mbutterick/fontkit/blob/master/src/tables/prep.js +|# + +(define prep (x:struct 'controlValueProgram (x:array #:type uint8))) + +(module+ test + (require rackunit racket/dict racket/serialize "../helper.rkt") + (define ip (open-input-file charter-path)) + (define dir (deserialize (read (open-input-file charter-directory-path)))) + (define offset (hash-ref (hash-ref (hash-ref dir 'tables) 'prep) 'offset)) + (define len (hash-ref (hash-ref (hash-ref dir 'tables) 'prep) 'length)) + (check-equal? offset 4512) + (check-equal? len 78) + (file-position ip 0) + (define table-bytes #"\270\0\0+\0\272\0\1\0\1\0\2+\1\272\0\2\0\1\0\2+\1\277\0\2\0C\0007\0+\0\37\0\23\0\0\0\b+\0\277\0\1\0\200\0i\0R\0;\0#\0\0\0\b+\0\272\0\3\0\5\0\a+\270\0\0 E}i\30D") + (check-equal? table-bytes (peek-bytes len offset ip)) + (define ds (open-input-bytes (peek-bytes len offset ip))) + (check-equal? (dict-ref (decode prep ds) 'controlValueProgram) '(184 0 0 43 0 186 0 1 0 1 0 2 43 1 186 0 2 0 1 0 2 43 1 191 0 2 0 67 0 55 0 43 0 31 0 19 0 0 0 8 43 0 191 0 1 0 128 0 105 0 82 0 59 0 35 0 0 0 8 43 0 186 0 3 0 5 0 7 43 184 0 0 32 69 125 105 24 68))) diff --git a/fontland/fontland/tables.rkt b/fontland/fontland/tables.rkt new file mode 100644 index 00000000..b0d56687 --- /dev/null +++ b/fontland/fontland/tables.rkt @@ -0,0 +1,29 @@ +#lang racket/base +(require (for-syntax racket/base racket/string) "helper.rkt") +(provide (all-defined-out)) + +#| +approximates +https://github.com/mbutterick/fontkit/blob/master/src/tables/index.js +|# + +(define-syntax (define-table-codecs stx) + (syntax-case stx () + [(_ ID TABLE-ID ...) + (with-syntax ([(TABLE-ID-STRING ...) (map (λ (s) (datum->syntax stx (string-append "table/" (string-replace (format "~a.rkt" (syntax->datum s)) "/" "")))) + (syntax->list #'(TABLE-ID ...)))]) + #'(begin + (r+p TABLE-ID-STRING ...) + (define ID (make-hasheq (map cons (list 'TABLE-ID ...) (list TABLE-ID ...))))))])) + +(define-table-codecs table-codecs head hhea hmtx maxp OS/2 post cvt_ fpgm loca prep glyf CFF_) + + +#| +Tables not supported: +cmap name +PostScript outlines: +CFF2 VORG +Advanced OpenType Tables +BASE GDEF GPOS GSUB JSTF +|# \ No newline at end of file diff --git a/fontland/fontland/test/data/FiraSans/FiraSans-Regular.ttf b/fontland/fontland/test/data/FiraSans/FiraSans-Regular.ttf new file mode 100755 index 00000000..7544de9c Binary files /dev/null and b/fontland/fontland/test/data/FiraSans/FiraSans-Regular.ttf differ diff --git a/fontland/fontland/test/data/FiraSans/OFL.txt b/fontland/fontland/test/data/FiraSans/OFL.txt new file mode 100755 index 00000000..a2c1ae22 --- /dev/null +++ b/fontland/fontland/test/data/FiraSans/OFL.txt @@ -0,0 +1,93 @@ +Copyright (c) 2012-2015, The Mozilla Foundation and Telefonica S.A. + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/fontland/fontland/test/data/Khmer/Khmer.ttf b/fontland/fontland/test/data/Khmer/Khmer.ttf new file mode 100755 index 00000000..e127df46 Binary files /dev/null and b/fontland/fontland/test/data/Khmer/Khmer.ttf differ diff --git a/fontland/fontland/test/data/Khmer/OFL.txt b/fontland/fontland/test/data/Khmer/OFL.txt new file mode 100755 index 00000000..53945192 --- /dev/null +++ b/fontland/fontland/test/data/Khmer/OFL.txt @@ -0,0 +1,94 @@ +Copyright (c) 2010, Danh Hong (khmertype.blogspot.com), +with Reserved Font Name Khmer. + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/fontland/fontland/test/data/Mada/Mada-Regular.subset1.ttf b/fontland/fontland/test/data/Mada/Mada-Regular.subset1.ttf new file mode 100755 index 00000000..c89496ae Binary files /dev/null and b/fontland/fontland/test/data/Mada/Mada-Regular.subset1.ttf differ diff --git a/fontland/fontland/test/data/Mada/Mada-VF.ttf b/fontland/fontland/test/data/Mada/Mada-VF.ttf new file mode 100755 index 00000000..e6f44045 Binary files /dev/null and b/fontland/fontland/test/data/Mada/Mada-VF.ttf differ diff --git a/fontland/fontland/test/data/Mada/OFL.txt b/fontland/fontland/test/data/Mada/OFL.txt new file mode 100755 index 00000000..2cf10c35 --- /dev/null +++ b/fontland/fontland/test/data/Mada/OFL.txt @@ -0,0 +1,87 @@ +Copyright © 2015-2016 The Mada Project Authors, with Reserved Font Name "Source". Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries. + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +--------------------------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +--------------------------------------------------------------------------- + +PREAMBLE + +The goals of the Open Font License (OFL) are to stimulate worldwide development +of collaborative font projects, to support the font creation efforts of academic +and linguistic communities, and to provide a free and open framework in which +fonts may be shared and improved in partnership with others. + +The OFL allows the licensed fonts to be used, studied, modified and redistributed +freely as long as they are not sold by themselves. The fonts, including any +derivative works, can be bundled, embedded, redistributed and/or sold with any +software provided that any reserved names are not used by derivative works. The +fonts and derivatives, however, cannot be released under any other type of license. +The requirement for fonts to remain under this license does not apply to any +document created using the fonts or their derivatives. + +DEFINITIONS + +"Font Software" refers to the set of files released by the Copyright Holder(s) under +this license and clearly marked as such. This may include source files, build +scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the copyright +statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, or +substituting -- in part or in whole -- any of the components of the Original Version, +by changing formats or by porting the Font Software to a new environment. + +"Author" refers to any designer, engineer, programmer, technical writer or other +person who contributed to the Font Software. + +PERMISSION & CONDITIONS + +Permission is hereby granted, free of charge, to any person obtaining a copy of the +Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell +modified and unmodified copies of the Font Software, subject to the following +conditions: + +1) Neither the Font Software nor any of its individual components, in Original or +Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, redistributed +and/or sold with any software, provided that each copy contains the above copyright +notice and this license. These can be included either as stand-alone text files, +human-readable headers or in the appropriate machine-readable metadata fields within +text or binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless +explicit written permission is granted by the corresponding Copyright Holder. This +restriction only applies to the primary font name as presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall +not be used to promote, endorse or advertise any Modified Version, except to +acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with +their explicit written permission. + +5) The Font Software, modified or unmodified, in part or in whole, must be distributed +entirely under this license, and must not be distributed under any other license. The +requirement for fonts to remain under this license does not apply to any document +created using the Font Software. + +TERMINATION + +This license becomes null and void if any of the above conditions are not met. + +DISCLAIMER + +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER +RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR +INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/fontland/fontland/test/data/NotoSans/LICENSE.txt b/fontland/fontland/test/data/NotoSans/LICENSE.txt new file mode 100755 index 00000000..d6456956 --- /dev/null +++ b/fontland/fontland/test/data/NotoSans/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/fontland/fontland/test/data/NotoSans/NotoKufiArabic-Regular.ttf b/fontland/fontland/test/data/NotoSans/NotoKufiArabic-Regular.ttf new file mode 100755 index 00000000..85b4ffd8 Binary files /dev/null and b/fontland/fontland/test/data/NotoSans/NotoKufiArabic-Regular.ttf differ diff --git a/fontland/fontland/test/data/NotoSans/NotoNastaliqUrduDraft.ttf b/fontland/fontland/test/data/NotoSans/NotoNastaliqUrduDraft.ttf new file mode 100755 index 00000000..0acb630f Binary files /dev/null and b/fontland/fontland/test/data/NotoSans/NotoNastaliqUrduDraft.ttf differ diff --git a/fontland/fontland/test/data/NotoSans/NotoSans.dfont b/fontland/fontland/test/data/NotoSans/NotoSans.dfont new file mode 100755 index 00000000..2ecbfd23 Binary files /dev/null and b/fontland/fontland/test/data/NotoSans/NotoSans.dfont differ diff --git a/fontland/fontland/test/data/NotoSans/NotoSans.ttc b/fontland/fontland/test/data/NotoSans/NotoSans.ttc new file mode 100755 index 00000000..ba2dc98d Binary files /dev/null and b/fontland/fontland/test/data/NotoSans/NotoSans.ttc differ diff --git a/fontland/fontland/test/data/NotoSans/NotoSansBalinese-Regular.ttf b/fontland/fontland/test/data/NotoSans/NotoSansBalinese-Regular.ttf new file mode 100755 index 00000000..0b0f58f0 Binary files /dev/null and b/fontland/fontland/test/data/NotoSans/NotoSansBalinese-Regular.ttf differ diff --git a/fontland/fontland/test/data/NotoSans/NotoSansBengali-Regular.ttf b/fontland/fontland/test/data/NotoSans/NotoSansBengali-Regular.ttf new file mode 100755 index 00000000..320b43e3 Binary files /dev/null and b/fontland/fontland/test/data/NotoSans/NotoSansBengali-Regular.ttf differ diff --git a/fontland/fontland/test/data/NotoSans/NotoSansDevanagari-Regular.ttf b/fontland/fontland/test/data/NotoSans/NotoSansDevanagari-Regular.ttf new file mode 100755 index 00000000..19a50993 Binary files /dev/null and b/fontland/fontland/test/data/NotoSans/NotoSansDevanagari-Regular.ttf differ diff --git a/fontland/fontland/test/data/NotoSans/NotoSansGujarati-Regular.ttf b/fontland/fontland/test/data/NotoSans/NotoSansGujarati-Regular.ttf new file mode 100755 index 00000000..11fe8c7f Binary files /dev/null and b/fontland/fontland/test/data/NotoSans/NotoSansGujarati-Regular.ttf differ diff --git a/fontland/fontland/test/data/NotoSans/NotoSansGurmukhi-Regular.ttf b/fontland/fontland/test/data/NotoSans/NotoSansGurmukhi-Regular.ttf new file mode 100755 index 00000000..39a7ed39 Binary files /dev/null and b/fontland/fontland/test/data/NotoSans/NotoSansGurmukhi-Regular.ttf differ diff --git a/fontland/fontland/test/data/NotoSans/NotoSansKannada-Regular.ttf b/fontland/fontland/test/data/NotoSans/NotoSansKannada-Regular.ttf new file mode 100755 index 00000000..7366f378 Binary files /dev/null and b/fontland/fontland/test/data/NotoSans/NotoSansKannada-Regular.ttf differ diff --git a/fontland/fontland/test/data/NotoSans/NotoSansKhmer-Regular.ttf b/fontland/fontland/test/data/NotoSans/NotoSansKhmer-Regular.ttf new file mode 100755 index 00000000..e665ed78 Binary files /dev/null and b/fontland/fontland/test/data/NotoSans/NotoSansKhmer-Regular.ttf differ diff --git a/fontland/fontland/test/data/NotoSans/NotoSansMalayalam-Regular.ttf b/fontland/fontland/test/data/NotoSans/NotoSansMalayalam-Regular.ttf new file mode 100755 index 00000000..14eb2d10 Binary files /dev/null and b/fontland/fontland/test/data/NotoSans/NotoSansMalayalam-Regular.ttf differ diff --git a/fontland/fontland/test/data/NotoSans/NotoSansMongolian-Regular.ttf b/fontland/fontland/test/data/NotoSans/NotoSansMongolian-Regular.ttf new file mode 100755 index 00000000..804dccbd Binary files /dev/null and b/fontland/fontland/test/data/NotoSans/NotoSansMongolian-Regular.ttf differ diff --git a/fontland/fontland/test/data/NotoSans/NotoSansNKo-Regular.ttf b/fontland/fontland/test/data/NotoSans/NotoSansNKo-Regular.ttf new file mode 100755 index 00000000..3ea23823 Binary files /dev/null and b/fontland/fontland/test/data/NotoSans/NotoSansNKo-Regular.ttf differ diff --git a/fontland/fontland/test/data/NotoSans/NotoSansOriya-Regular.ttf b/fontland/fontland/test/data/NotoSans/NotoSansOriya-Regular.ttf new file mode 100755 index 00000000..ab57812e Binary files /dev/null and b/fontland/fontland/test/data/NotoSans/NotoSansOriya-Regular.ttf differ diff --git a/fontland/fontland/test/data/NotoSans/NotoSansPhagsPa-Regular.ttf b/fontland/fontland/test/data/NotoSans/NotoSansPhagsPa-Regular.ttf new file mode 100755 index 00000000..030ab88b Binary files /dev/null and b/fontland/fontland/test/data/NotoSans/NotoSansPhagsPa-Regular.ttf differ diff --git a/fontland/fontland/test/data/NotoSans/NotoSansSyriacEstrangela-Regular.ttf b/fontland/fontland/test/data/NotoSans/NotoSansSyriacEstrangela-Regular.ttf new file mode 100755 index 00000000..dcb33107 Binary files /dev/null and b/fontland/fontland/test/data/NotoSans/NotoSansSyriacEstrangela-Regular.ttf differ diff --git a/fontland/fontland/test/data/NotoSans/NotoSansTamil-Regular.ttf b/fontland/fontland/test/data/NotoSans/NotoSansTamil-Regular.ttf new file mode 100755 index 00000000..b8259eac Binary files /dev/null and b/fontland/fontland/test/data/NotoSans/NotoSansTamil-Regular.ttf differ diff --git a/fontland/fontland/test/data/NotoSans/NotoSansTelugu-Regular.ttf b/fontland/fontland/test/data/NotoSans/NotoSansTelugu-Regular.ttf new file mode 100755 index 00000000..bba7e0c5 Binary files /dev/null and b/fontland/fontland/test/data/NotoSans/NotoSansTelugu-Regular.ttf differ diff --git a/fontland/fontland/test/data/NotoSans/NotoSerifKannada-Regular.ttf b/fontland/fontland/test/data/NotoSans/NotoSerifKannada-Regular.ttf new file mode 100755 index 00000000..30ca5509 Binary files /dev/null and b/fontland/fontland/test/data/NotoSans/NotoSerifKannada-Regular.ttf differ diff --git a/fontland/fontland/test/data/NotoSansCJK/NotoSansCJKkr-Regular.otf b/fontland/fontland/test/data/NotoSansCJK/NotoSansCJKkr-Regular.otf new file mode 100755 index 00000000..a98f833c Binary files /dev/null and b/fontland/fontland/test/data/NotoSansCJK/NotoSansCJKkr-Regular.otf differ diff --git a/fontland/fontland/test/data/NotoSansCJK/OFL.txt b/fontland/fontland/test/data/NotoSansCJK/OFL.txt new file mode 100755 index 00000000..d952d62c --- /dev/null +++ b/fontland/fontland/test/data/NotoSansCJK/OFL.txt @@ -0,0 +1,92 @@ +This Font Software is licensed under the SIL Open Font License, +Version 1.1. + +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font +creation efforts of academic and linguistic communities, and to +provide a free and open framework in which fonts may be shared and +improved in partnership with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply to +any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software +components as distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, +deleting, or substituting -- in part or in whole -- any of the +components of the Original Version, by changing formats or by porting +the Font Software to a new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, +modify, redistribute, and sell modified and unmodified copies of the +Font Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, in +Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the +corresponding Copyright Holder. This restriction only applies to the +primary font name as presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created using +the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/fontland/fontland/test/data/OpenSans/LICENSE.txt b/fontland/fontland/test/data/OpenSans/LICENSE.txt new file mode 100755 index 00000000..75b52484 --- /dev/null +++ b/fontland/fontland/test/data/OpenSans/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/fontland/fontland/test/data/OpenSans/OpenSans-Regular.ttf b/fontland/fontland/test/data/OpenSans/OpenSans-Regular.ttf new file mode 100755 index 00000000..db433349 Binary files /dev/null and b/fontland/fontland/test/data/OpenSans/OpenSans-Regular.ttf differ diff --git a/fontland/fontland/test/data/Play/OFL.txt b/fontland/fontland/test/data/Play/OFL.txt new file mode 100755 index 00000000..116aa293 --- /dev/null +++ b/fontland/fontland/test/data/Play/OFL.txt @@ -0,0 +1,94 @@ +Copyright (c) 2011, Jonas Hecksher, Playtypes, e-types AS (e-types.com), +with Reserved Font Name "Play", "Playtype", "Playtype Sans". + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/fontland/fontland/test/data/Play/Play-Regular.ttf b/fontland/fontland/test/data/Play/Play-Regular.ttf new file mode 100755 index 00000000..57ab4b1a Binary files /dev/null and b/fontland/fontland/test/data/Play/Play-Regular.ttf differ diff --git a/fontland/fontland/test/data/PlayfairDisplay/OFL.txt b/fontland/fontland/test/data/PlayfairDisplay/OFL.txt new file mode 100755 index 00000000..cfeec241 --- /dev/null +++ b/fontland/fontland/test/data/PlayfairDisplay/OFL.txt @@ -0,0 +1,93 @@ +Copyright (c) 2010-2013 by Claus Eggers Sørensen (es@forthehearts.net), with Reserved Font Name 'Playfair' + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/fontland/fontland/test/data/PlayfairDisplay/PlayfairDisplay-Regular.otf b/fontland/fontland/test/data/PlayfairDisplay/PlayfairDisplay-Regular.otf new file mode 100755 index 00000000..a3e5773c Binary files /dev/null and b/fontland/fontland/test/data/PlayfairDisplay/PlayfairDisplay-Regular.otf differ diff --git a/fontland/fontland/test/data/README.md b/fontland/fontland/test/data/README.md new file mode 100755 index 00000000..9048e823 --- /dev/null +++ b/fontland/fontland/test/data/README.md @@ -0,0 +1,3 @@ +The fonts in this directory are free and open source fonts used for testing purposes. +The goal is to test as much of fontkit's functionality as possible. The licenses for these +fonts are included in the subfolders for each family, along with the font files themselves. diff --git a/fontland/fontland/test/data/SourceSansPro/OFL.txt b/fontland/fontland/test/data/SourceSansPro/OFL.txt new file mode 100755 index 00000000..d154618a --- /dev/null +++ b/fontland/fontland/test/data/SourceSansPro/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries. + +This Font Software is licensed under the SIL Open Font License, Version 1.1. + +This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/fontland/fontland/test/data/SourceSansPro/SourceSansPro-Regular.otf b/fontland/fontland/test/data/SourceSansPro/SourceSansPro-Regular.otf new file mode 100755 index 00000000..55fc5339 Binary files /dev/null and b/fontland/fontland/test/data/SourceSansPro/SourceSansPro-Regular.otf differ diff --git a/fontland/fontland/test/data/SourceSansPro/SourceSansPro-Regular.woff b/fontland/fontland/test/data/SourceSansPro/SourceSansPro-Regular.woff new file mode 100755 index 00000000..3e03cc0f Binary files /dev/null and b/fontland/fontland/test/data/SourceSansPro/SourceSansPro-Regular.woff differ diff --git a/fontland/fontland/test/data/SourceSansPro/SourceSansPro-Regular.woff2 b/fontland/fontland/test/data/SourceSansPro/SourceSansPro-Regular.woff2 new file mode 100755 index 00000000..a6be1c24 Binary files /dev/null and b/fontland/fontland/test/data/SourceSansPro/SourceSansPro-Regular.woff2 differ diff --git a/fontland/fontland/test/data/amiri/OFL.txt b/fontland/fontland/test/data/amiri/OFL.txt new file mode 100755 index 00000000..9b1f2d10 --- /dev/null +++ b/fontland/fontland/test/data/amiri/OFL.txt @@ -0,0 +1,87 @@ +Copyright (c) 2010-2013, Khaled Hosny () + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +--------------------------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +--------------------------------------------------------------------------- + +PREAMBLE + +The goals of the Open Font License (OFL) are to stimulate worldwide development +of collaborative font projects, to support the font creation efforts of academic +and linguistic communities, and to provide a free and open framework in which +fonts may be shared and improved in partnership with others. + +The OFL allows the licensed fonts to be used, studied, modified and redistributed +freely as long as they are not sold by themselves. The fonts, including any +derivative works, can be bundled, embedded, redistributed and/or sold with any +software provided that any reserved names are not used by derivative works. The +fonts and derivatives, however, cannot be released under any other type of license. +The requirement for fonts to remain under this license does not apply to any +document created using the fonts or their derivatives. + +DEFINITIONS + +"Font Software" refers to the set of files released by the Copyright Holder(s) under +this license and clearly marked as such. This may include source files, build +scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the copyright +statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, or +substituting -- in part or in whole -- any of the components of the Original Version, +by changing formats or by porting the Font Software to a new environment. + +"Author" refers to any designer, engineer, programmer, technical writer or other +person who contributed to the Font Software. + +PERMISSION & CONDITIONS + +Permission is hereby granted, free of charge, to any person obtaining a copy of the +Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell +modified and unmodified copies of the Font Software, subject to the following +conditions: + +1) Neither the Font Software nor any of its individual components, in Original or +Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, redistributed +and/or sold with any software, provided that each copy contains the above copyright +notice and this license. These can be included either as stand-alone text files, +human-readable headers or in the appropriate machine-readable metadata fields within +text or binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless +explicit written permission is granted by the corresponding Copyright Holder. This +restriction only applies to the primary font name as presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall +not be used to promote, endorse or advertise any Modified Version, except to +acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with +their explicit written permission. + +5) The Font Software, modified or unmodified, in part or in whole, must be distributed +entirely under this license, and must not be distributed under any other license. The +requirement for fonts to remain under this license does not apply to any document +created using the Font Software. + +TERMINATION + +This license becomes null and void if any of the above conditions are not met. + +DISCLAIMER + +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER +RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR +INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/fontland/fontland/test/data/amiri/amiri-regular.ttf b/fontland/fontland/test/data/amiri/amiri-regular.ttf new file mode 100755 index 00000000..24c69a1e Binary files /dev/null and b/fontland/fontland/test/data/amiri/amiri-regular.ttf differ diff --git a/fontland/fontland/test/data/fonttest/AdobeVFPrototype-Subset.otf b/fontland/fontland/test/data/fonttest/AdobeVFPrototype-Subset.otf new file mode 100755 index 00000000..5cc7279f Binary files /dev/null and b/fontland/fontland/test/data/fonttest/AdobeVFPrototype-Subset.otf differ diff --git a/fontland/fontland/test/data/fonttest/LICENSE.txt b/fontland/fontland/test/data/fonttest/LICENSE.txt new file mode 100755 index 00000000..149567b4 --- /dev/null +++ b/fontland/fontland/test/data/fonttest/LICENSE.txt @@ -0,0 +1,13 @@ +Copyright 2016 Unicode Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the “License”); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an “AS IS” BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/fontland/fontland/test/data/fonttest/TestCMAP14.otf b/fontland/fontland/test/data/fonttest/TestCMAP14.otf new file mode 100755 index 00000000..da485d9e Binary files /dev/null and b/fontland/fontland/test/data/fonttest/TestCMAP14.otf differ diff --git a/fontland/fontland/test/data/fonttest/TestCMAPMacTurkish.ttf b/fontland/fontland/test/data/fonttest/TestCMAPMacTurkish.ttf new file mode 100755 index 00000000..4f89b093 Binary files /dev/null and b/fontland/fontland/test/data/fonttest/TestCMAPMacTurkish.ttf differ diff --git a/fontland/fontland/test/data/fonttest/TestGVARFour.ttf b/fontland/fontland/test/data/fonttest/TestGVARFour.ttf new file mode 100755 index 00000000..3524f374 Binary files /dev/null and b/fontland/fontland/test/data/fonttest/TestGVARFour.ttf differ diff --git a/fontland/fontland/test/data/fonttest/TestGVAROne.ttf b/fontland/fontland/test/data/fonttest/TestGVAROne.ttf new file mode 100755 index 00000000..17a481b6 Binary files /dev/null and b/fontland/fontland/test/data/fonttest/TestGVAROne.ttf differ diff --git a/fontland/fontland/test/data/fonttest/TestGVARThree.ttf b/fontland/fontland/test/data/fonttest/TestGVARThree.ttf new file mode 100755 index 00000000..17f5013d Binary files /dev/null and b/fontland/fontland/test/data/fonttest/TestGVARThree.ttf differ diff --git a/fontland/fontland/test/data/fonttest/TestGVARTwo.ttf b/fontland/fontland/test/data/fonttest/TestGVARTwo.ttf new file mode 100755 index 00000000..6061a166 Binary files /dev/null and b/fontland/fontland/test/data/fonttest/TestGVARTwo.ttf differ diff --git a/fontland/fontland/test/data/fonttest/TestHVARTwo.ttf b/fontland/fontland/test/data/fonttest/TestHVARTwo.ttf new file mode 100755 index 00000000..2e81f94c Binary files /dev/null and b/fontland/fontland/test/data/fonttest/TestHVARTwo.ttf differ diff --git a/fontland/fontland/test/data/ss-emoji/OFL.txt b/fontland/fontland/test/data/ss-emoji/OFL.txt new file mode 100755 index 00000000..193ba3e2 --- /dev/null +++ b/fontland/fontland/test/data/ss-emoji/OFL.txt @@ -0,0 +1,117 @@ + +--- +Symbolset +www.symbolset.com +Copyright © 2014 Oak Studios LLC +SS Emoji is a trademark of Oak Studios LLC in the United States and/or other countries. +--- + + +SS Emoji Beta v0.001 + + +-------------------- +License +-------------------- + + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name 'SS Emoji'" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. + +CONTACT +If you have any questions or comments about this Privacy Policy, you can contact us at help@symbolset.com. + +Symbolset +10 Jay St., #606 +Brooklyn, NY 11201 + +http://symbolset.com diff --git a/fontland/fontland/test/data/ss-emoji/ss-emoji-apple.ttf b/fontland/fontland/test/data/ss-emoji/ss-emoji-apple.ttf new file mode 100755 index 00000000..941fc4ec Binary files /dev/null and b/fontland/fontland/test/data/ss-emoji/ss-emoji-apple.ttf differ diff --git a/fontland/fontland/test/data/ss-emoji/ss-emoji-microsoft.ttf b/fontland/fontland/test/data/ss-emoji/ss-emoji-microsoft.ttf new file mode 100755 index 00000000..ab197a84 Binary files /dev/null and b/fontland/fontland/test/data/ss-emoji/ss-emoji-microsoft.ttf differ diff --git a/fontland/fontland/test/directory.rkt b/fontland/fontland/test/directory.rkt new file mode 100755 index 00000000..330a38cc --- /dev/null +++ b/fontland/fontland/test/directory.rkt @@ -0,0 +1,24 @@ +#lang racket +(require rackunit racket/runtime-path fontland) + +#| +approximates +https://github.com/mbutterick/fontkit/blob/master/test/directory.js +|# + +(define-runtime-path open-sans-ttf "data/OpenSans/OpenSans-Regular.ttf") + +(define font (open-font open-sans-ttf)) + +(test-case + "decodes SFNT directory values correctly" + (define dir (font-directory font)) + (check-equal? (hash-ref dir 'numTables) 19) + (check-equal? (hash-ref dir 'searchRange) 256) + (check-equal? (hash-ref dir 'entrySelector) 4) + (check-equal? (hash-ref dir 'rangeShift) 48)) + +(test-case + "numTables matches table collection" + (define dir (font-directory font)) + (check-equal? (length (hash-keys (hash-ref dir 'tables))) (hash-ref dir 'numTables))) diff --git a/fontland/fontland/test/glyph_mapping.rkt b/fontland/fontland/test/glyph_mapping.rkt new file mode 100755 index 00000000..55e3d3d7 --- /dev/null +++ b/fontland/fontland/test/glyph_mapping.rkt @@ -0,0 +1,149 @@ +#lang racket +(require rackunit racket/runtime-path fontland) + +#| +approximates +https://github.com/mbutterick/fontkit/blob/master/test/glyph_mapping.js +|# + +(define-runtime-path open-sans-ttf "data/OpenSans/OpenSans-Regular.ttf") +(define-runtime-path source-sans-otf "data/SourceSansPro/SourceSansPro-Regular.otf") +(define-runtime-path mada-ttf "data/Mada/Mada-Regular.subset1.ttf") + + (define font (open-font open-sans-ttf)) + +#| +import fontkit from '../src'; +import assert from 'assert'; + +describe('character to glyph mapping', function() { + describe('basic cmap handling', function() { + let font = fontkit.openSync(__dirname + '/data/OpenSans/OpenSans-Regular.ttf'); + + it('should get characterSet', function() { + assert(Array.isArray(font.characterSet)); + return assert.equal(font.characterSet.length, 884); + }); + + it('should check if a character is supported', function() { + assert(font.hasGlyphForCodePoint('a'.charCodeAt())); + return assert(!font.hasGlyphForCodePoint(0)); + }); + + it('should get a glyph for a character code', function() { + let glyph = font.glyphForCodePoint('a'.charCodeAt()); + assert.equal(glyph.id, 68); + return assert.deepEqual(glyph.codePoints, [97]); + }); + + it('should map a string to glyphs', function() { + let glyphs = font.glyphsForString('hello', []); + assert(Array.isArray(glyphs)); + assert.equal(glyphs.length, 5); + assert.deepEqual(glyphs.map(g => g.id), [75, 72, 79, 79, 82]); + return assert.deepEqual(glyphs.map(g => g.codePoints), [[104], [101], [108], [108], [111]]); + }); + + it('should support unicode variation selectors', function() { + let font = fontkit.openSync(__dirname + '/data/fonttest/TestCMAP14.otf'); + let glyphs = font.glyphsForString('\u{82a6}\u{82a6}\u{E0100}\u{82a6}\u{E0101}'); + assert.deepEqual(glyphs.map(g => g.id), [1, 1, 2]); + }); + + it('should support legacy encodings when no unicode cmap is found', function() { + let font = fontkit.openSync(__dirname + '/data/fonttest/TestCMAPMacTurkish.ttf'); + let glyphs = font.glyphsForString("“ABÇĞIİÖŞÜ”"); + assert.deepEqual(glyphs.map(g => g.id), [200, 34, 35, 126, 176, 42, 178, 140, 181, 145, 201]); + }); + }); + + describe('opentype features', function() { + let font = fontkit.openSync(__dirname + '/data/SourceSansPro/SourceSansPro-Regular.otf'); + + it('should list available features', () => + assert.deepEqual(font.availableFeatures, [ + 'aalt', 'c2sc', 'case', 'ccmp', 'dnom', 'frac', 'liga', 'numr', + 'onum', 'ordn', 'pnum', 'salt', 'sinf', 'smcp', 'ss01', 'ss02', + 'ss03', 'ss04', 'ss05', 'subs', 'sups', 'zero', 'kern', 'mark', + 'mkmk', 'size' + ]) + ); + + it('should apply opentype GSUB features', function() { + let {glyphs} = font.layout('ffi', ['dlig']); + assert.equal(glyphs.length, 2); + assert.deepEqual(glyphs.map(g => g.id), [ 514, 36 ]); + return assert.deepEqual(glyphs.map(g => g.codePoints), [[102, 102], [105]]); + }); + + it('should enable fractions when using fraction slash', function() { + let {glyphs} = font.layout('123 1⁄16 123'); + return assert.deepEqual(glyphs.map(g => g.id), [ 1088, 1089, 1090, 1, 1617, 1724, 1603, 1608, 1, 1088, 1089, 1090 ]); + }); + + it('should not break if can’t enable fractions when using fraction slash', function() { + let {glyphs} = font.layout('a⁄b ⁄ 1⁄ ⁄2'); + return assert.deepEqual(glyphs.map(g => g.id), [ 28, 1724, 29, 1, 1724, 1, 1617, 1724, 1, 1724, 1604 ]); + }); + }); + + describe('AAT features', function() { + let font = fontkit.openSync(__dirname + '/data/Play/Play-Regular.ttf'); + + it('should list available features', () => assert.deepEqual(font.availableFeatures, [ 'tnum', 'sups', 'subs', 'numr', 'onum', 'lnum', 'liga', 'kern' ])); + + it('should apply default AAT morx features', function() { + let {glyphs} = font.layout('ffi 1⁄2'); + assert.equal(glyphs.length, 5); + assert.deepEqual(glyphs.map(g => g.id), [ 767, 3, 20, 645, 21 ]); + return assert.deepEqual(glyphs.map(g => g.codePoints), [[102, 102, 105], [32], [49], [8260], [50]]); + }); + + it('should apply user specified features', function() { + let {glyphs} = font.layout('ffi 1⁄2', [ 'numr' ]); + assert.equal(glyphs.length, 3); + assert.deepEqual(glyphs.map(g => g.id), [ 767, 3, 126 ]); + return assert.deepEqual(glyphs.map(g => g.codePoints), [[102, 102, 105], [32], [49, 8260, 50]]); + }); + + it('should handle rtl direction', function() { + let {glyphs} = font.layout('ffi', [], null, null, "rtl"); + assert.equal(glyphs.length, 3); + assert.deepEqual(glyphs.map(g => g.id), [ 76, 73, 73 ]); + return assert.deepEqual(glyphs.map(g => g.codePoints), [[105], [102], [102]]); + }); + + it('should apply indic reordering features', function() { + let f = fontkit.openSync(__dirname + '/data/Khmer/Khmer.ttf'); + let {glyphs} = f.layout('ខ្ញុំអាចញ៉ាំកញ្ចក់បាន ដោយគ្មានបញ្ហា'); + assert.deepEqual(glyphs.map(g => g.id), [ + 45, 153, 177, 112, 248, 188, 49, 296, 44, 187, 149, 44, 117, 236, 188, 63, 3, 107, + 226, 188, 69, 218, 169, 188, 63, 64, 255, 175, 188 + ]); + + return assert.deepEqual(glyphs.map(g => g.codePoints), [ + [ 6017 ], [ 6098, 6025 ], [ 6075 ], [ 6086 ], [ 6050 ], [ 6070 ], [ 6021 ], + [ 6025, 6089, 6070, 6086 ], [ 6016 ], [ 6025 ], [ 6098, 6021 ], [ 6016 ], + [ 6091 ], [ 6036 ], [ 6070 ], [ 6035 ], [ 32 ], [ 6084 ], [ 6026 ], [ 6070 ], + [ 6041 ], [ 6018 ], [ 6098, 6040 ], [ 6070 ], [ 6035 ], [ 6036 ], [ 6025 ], + [ 6098, 6048 ], [ 6070 ] + ]); + }); + }); + + describe('glyph id to strings', function () { + it('should return strings from cmap that map to a given glyph', function () { + let font = fontkit.openSync(__dirname + '/data/OpenSans/OpenSans-Regular.ttf'); + let strings = font.stringsForGlyph(68); + assert.deepEqual(strings, ['a']); + }); + + it('should return strings from AAT morx table that map to the given glyph', function () { + let font = fontkit.openSync(__dirname + '/data/Play/Play-Regular.ttf'); + let strings = font.stringsForGlyph(767); + assert.deepEqual(strings, ['ffi']); + }); + }); +}); + +|# diff --git a/fontland/fontland/test/glyphs.rkt b/fontland/fontland/test/glyphs.rkt new file mode 100755 index 00000000..2023a906 --- /dev/null +++ b/fontland/fontland/test/glyphs.rkt @@ -0,0 +1,236 @@ +#lang racket +(require rackunit racket/runtime-path fontland fontland/cff-glyph fontland/path) + +#| +approximates +https://github.com/mbutterick/fontkit/blob/master/test/glyphs.js +|# + +(define-runtime-path open-sans-ttf "data/OpenSans/OpenSans-Regular.ttf") +(define-runtime-path mada-ttf "data/Mada/Mada-Regular.subset1.ttf") +(define-runtime-path source-otf "data/SourceSansPro/SourceSansPro-Regular.otf") + +(define font (open-font source-otf)) + (define glyph (get-glyph font 5)) + +(test-case + "should get a TTFGlyph" + (check-true (cff-glyph? glyph))) + +(test-case + "should get a path for the glyph" + (check-equal? (toSVG (getPath glyph)) + "M90 0L258 0C456 0 564 122 564 331C564 539 456 656 254 656L90 656ZM173 68L173 588L248 588C401 588 478 496 478 331C478 165 401 68 248 68Z")) + +#| +import fontkit from '../src'; +import assert from 'assert'; +import BBox from '../src/glyph/BBox'; + +describe('glyphs', function() { + describe('truetype glyphs', function() { + let font = fontkit.openSync(__dirname + '/data/OpenSans/OpenSans-Regular.ttf'); + let mada = fontkit.openSync(__dirname + '/data/Mada/Mada-VF.ttf'); + + it('should get a TTFGlyph', function() { + let glyph = font.getGlyph(39); // D + return assert.equal(glyph.constructor.name, 'TTFGlyph'); + }); + + it('should get a path for the glyph', function() { + let glyph = font.getGlyph(39); + return assert.equal(glyph.path.toSVG(), 'M1368 745Q1368 383 1171.5 191.5Q975 0 606 0L201 0L201 1462L649 1462Q990 1462 1179 1273Q1368 1084 1368 745ZM1188 739Q1188 1025 1044.5 1170Q901 1315 618 1315L371 1315L371 147L578 147Q882 147 1035 296.5Q1188 446 1188 739Z'); + }); + + it('should get a composite glyph', function() { + let glyph = font.getGlyph(171); // é + return assert.equal(glyph.path.toSVG(), 'M639 -20Q396 -20 255.5 128Q115 276 115 539Q115 804 245.5 960Q376 1116 596 1116Q802 1116 922 980.5Q1042 845 1042 623L1042 518L287 518Q292 325 384.5 225Q477 125 645 125Q822 125 995 199L995 51Q907 13 828.5 -3.5Q750 -20 639 -20ZM594 977Q462 977 383.5 891Q305 805 291 653L864 653Q864 810 794 893.5Q724 977 594 977ZM471 1266Q519 1328 574.5 1416Q630 1504 662 1569L864 1569L864 1548Q820 1483 733 1388Q646 1293 582 1241L471 1241Z'); + }); + + it('should resolve composite glyphs recursively', function () { + let r = mada.layout('ي'); + assert.equal(r.glyphs[0].path.toSVG(), 'M-140 0Q-140 -22 -125 -37Q-110 -52 -88 -52Q-66 -52 -51 -37Q-36 -22 -36 0Q-36 22 -51 37Q-66 52 -88 52Q-110 52 -125 37Q-140 22 -140 0ZM36 0Q36 -22 51 -37Q66 -52 88 -52Q110 -52 125 -37Q140 -22 140 0Q140 22 125 37Q110 52 88 52Q66 52 51 37Q36 22 36 0Z'); + }); + + it('should transform points of a composite glyph', function () { + let r = mada.layout('فا'); + assert.equal(r.glyphs[0].path.toSVG(), 'M155 624L155 84Q150 90 145.5 94.5Q141 99 136 105L292 105L292 0L156 0Q128 0 103.5 13.5Q79 27 64.5 50.5Q50 74 50 104L50 624ZM282 105L312 105L312 0L282 0Z'); + }); + + it('should be able to get a scaled path at a given font size', function () { + let glyph = font.getGlyph(39); + assert.equal(glyph.getScaledPath(1000).toSVG(), 'M667.97 363.77Q667.97 187.01 572.02 93.51Q476.07 0 295.9 0L98.14 0L98.14 713.87L316.89 713.87Q483.4 713.87 575.68 621.58Q667.97 529.3 667.97 363.77ZM580.08 360.84Q580.08 500.49 510.01 571.29Q439.94 642.09 301.76 642.09L181.15 642.09L181.15 71.78L282.23 71.78Q430.66 71.78 505.37 144.78Q580.08 217.77 580.08 360.84Z'); + }); + + it('should get the glyph cbox', function() { + let glyph = font.getGlyph(39); + return assert.deepEqual(glyph.cbox, new BBox(201, 0, 1368, 1462)); + }); + + it('should get the glyph bbox', function() { + let glyph = font.getGlyph(39); + return assert.deepEqual(glyph.bbox, new BBox(201, 0, 1368, 1462)); + }); + + it('should get correct bbox for runs containing blanks', function () { + let r = font.layout('abc ef'); + return assert.deepEqual(r.bbox, new BBox(94, -20, 5832, 1567)); + }); + + it('should get the advance width', function() { + let glyph = font.getGlyph(39); + return assert.equal(glyph.advanceWidth | 0, 1493); + }); + + it('should get the glyph name', function() { + let glyph = font.getGlyph(171); + return assert.equal(glyph.name, 'eacute'); + }); + }); + + describe('CFF glyphs', function() { + let font = fontkit.openSync(__dirname + '/data/SourceSansPro/SourceSansPro-Regular.otf'); + + it('should get a CFFGlyph', function() { + let glyph = font.getGlyph(5); // D + return assert.equal(glyph.constructor.name, 'CFFGlyph'); + }); + + it('should get a path for the glyph', function() { + let glyph = font.getGlyph(5); + return assert.equal(glyph.path.toSVG(), 'M90 0L258 0C456 0 564 122 564 331C564 539 456 656 254 656L90 656ZM173 68L173 588L248 588C401 588 478 496 478 331C478 165 401 68 248 68Z'); + }); + + it('should get the glyph cbox', function() { + let glyph = font.getGlyph(5); + return assert.deepEqual(glyph.cbox, new BBox(90, 0, 564, 656)); + }); + + it('should get the glyph bbox', function() { + let glyph = font.getGlyph(5); + return assert.deepEqual(glyph.bbox, new BBox(90, 0, 564, 656)); + }); + + it('should get the glyph name', function() { + let glyph = font.getGlyph(5); + return assert.equal(glyph.name, 'D'); + }); + }); + + describe('SBIX glyphs', function() { + let font = fontkit.openSync(__dirname + '/data/ss-emoji/ss-emoji-apple.ttf'); + + it('should get an SBIXGlyph', function() { + let glyph = font.glyphsForString('😜')[0]; + return assert.equal(glyph.constructor.name, 'SBIXGlyph'); + }); + + it('should have an empty path', function() { + let glyph = font.glyphsForString('😜')[0]; + return assert.equal(glyph.path.toSVG(), 'M0 2048ZM2055 -7Z'); + }); + + it('should get an image', function() { + let glyph = font.glyphsForString('😜')[0]; + let image = glyph.getImageForSize(32); + return assert.deepEqual(image, { + originX: 0, + originY: 0, + type: 'png ', + data: image.data + }); + }); + + it('should get the glyph name', function() { + let glyph = font.glyphsForString('😜')[0]; + return assert.equal(glyph.name, 'stuckouttonguewinkingeye'); + }); + }); + + describe('COLR glyphs', function() { + let font = fontkit.openSync(__dirname + '/data/ss-emoji/ss-emoji-microsoft.ttf'); + + it('should get an SBIXGlyph', function() { + let glyph = font.glyphsForString('😜')[0]; + return assert.equal(glyph.constructor.name, 'COLRGlyph'); + }); + + it('should get layers', function() { + let glyph = font.glyphsForString('😜')[0]; + return assert.deepEqual(glyph.layers, [ + { glyph: font.getGlyph(247), color: { red: 252, green: 194, blue: 0, alpha: 255 }}, + { glyph: font.getGlyph(248), color: { red: 159, green: 79, blue: 0, alpha: 255 }}, + { glyph: font.getGlyph(249), color: { red: 229, green: 65, blue: 65, alpha: 255 }} + ]); + }); + + it('should get empty path', function() { + let glyph = font.glyphsForString('😜')[0]; + return assert.equal(glyph.path.toSVG(), ''); + }); + + it('should get bbox', function() { + let glyph = font.glyphsForString('😜')[0]; + return assert.deepEqual(glyph.bbox, new BBox(0, 0, 2048, 2048)); + }); + + it('should get the glyph name', function() { + let glyph = font.glyphsForString('😜')[0]; + return assert.equal(glyph.name, 'stuckouttonguewinkingeye'); + }); + }); + + describe('WOFF glyphs', function() { + let font = fontkit.openSync(__dirname + '/data/SourceSansPro/SourceSansPro-Regular.woff'); + + it('should get a TTFGlyph', function() { + let glyph = font.glyphsForString('T')[0]; + return assert.equal(glyph.constructor.name, 'TTFGlyph'); + }); + + it('should get a path for the glyph', function() { + let glyph = font.glyphsForString('T')[0]; + return assert.equal(glyph.path.toSVG(), 'M226 586L28 586L28 656L508 656L508 586L310 586L310 0L226 0Z'); + }); + + it('should get the glyph name', function() { + let glyph = font.glyphsForString('T')[0]; + return assert.equal(glyph.name, 'T'); + }); + }); + + describe('WOFF2 glyph', function() { + let font = fontkit.openSync(__dirname + '/data/SourceSansPro/SourceSansPro-Regular.woff2'); + + it('should get a WOFF2Glyph', function() { + let glyph = font.glyphsForString('T')[0]; + return assert.equal(glyph.constructor.name, 'WOFF2Glyph'); + }); + + it('should get a path for the glyph', function() { + let glyph = font.glyphsForString('T')[0]; + return assert.equal(glyph.path.toSVG(), 'M226 586L28 586L28 656L508 656L508 586L310 586L310 0L226 0Z'); + }); + + it('should get a correct path for all contours', function() { + let glyph = font.glyphsForString('o')[0]; + return assert.equal(glyph.path.toSVG(), 'M271 -12Q226 -12 185.5 5Q145 22 114 54.5Q83 87 64.5 134.5Q46 182 46 242Q46 303 64.5 350.5Q83 398 114 431Q145 464 185.5 481Q226 498 271 498Q316 498 356.5 481Q397 464 428 431Q459 398 477.5 350.5Q496 303 496 242Q496 182 477.5 134.5Q459 87 428 54.5Q397 22 356.5 5Q316 -12 271 -12ZM271 56Q302 56 328 69.5Q354 83 372.5 107.5Q391 132 401 166Q411 200 411 242Q411 284 401 318.5Q391 353 372.5 378Q354 403 328 416.5Q302 430 271 430Q240 430 214 416.5Q188 403 169.5 378Q151 353 141 318.5Q131 284 131 242Q131 200 141 166Q151 132 169.5 107.5Q188 83 214 69.5Q240 56 271 56Z'); + }); + + it('should get the glyph cbox', function() { + let glyph = font.glyphsForString('T')[0]; + return assert.deepEqual(glyph.cbox, new BBox(28, 0, 508, 656)); + }); + + it('should get the glyph bbox', function() { + let glyph = font.glyphsForString('T')[0]; + return assert.deepEqual(glyph.bbox, new BBox(28, 0, 508, 656)); + }); + + it('should get the glyph name', function() { + let glyph = font.glyphsForString('T')[0]; + return assert.equal(glyph.name, 'T'); + }); + }); +}); +|# \ No newline at end of file diff --git a/fontland/fontland/test/index.rkt b/fontland/fontland/test/index.rkt new file mode 100755 index 00000000..d4f4bde0 --- /dev/null +++ b/fontland/fontland/test/index.rkt @@ -0,0 +1,30 @@ +#lang racket +(require rackunit racket/runtime-path fontland) + +#| +approximates +https://github.com/mbutterick/fontkit/blob/master/test/index.js +|# + +(define-runtime-path open-sans-ttf "data/OpenSans/OpenSans-Regular.ttf") +(define-runtime-path source-sans-otf "data/SourceSansPro/SourceSansPro-Regular.otf") +(define-runtime-path mada-ttf "data/Mada/Mada-Regular.subset1.ttf") + +(test-case + "should open a font" + (check-true (ttf-font? (open-font open-sans-ttf)))) + +(test-case + "should open fonts of different formats" + (check-true (ttf-font? (open-font open-sans-ttf))) + (check-true (ttf-font? (open-font source-sans-otf)))) + +(test-case + "should open fonts lacking PostScript name" + (define font (open-font mada-ttf)) + (check-false (font-postscript-name font))) + +#;(test-case + "should error when opening an invalid font asynchronously" + (check-exn exn:fail? (λ () (open-font "nowhere")))) + diff --git a/fontland/fontland/test/metadata.rkt b/fontland/fontland/test/metadata.rkt new file mode 100755 index 00000000..2b6fdcda --- /dev/null +++ b/fontland/fontland/test/metadata.rkt @@ -0,0 +1,48 @@ +#lang racket +(require rackunit racket/runtime-path fontland) + +#| +approximates +https://github.com/mbutterick/fontkit/blob/master/test/metadata.js +|# + +#| +import fontkit from '../src'; +import assert from 'assert'; +import BBox from '../src/glyph/BBox'; + +describe('metadata', function() { + let font = fontkit.openSync(__dirname + '/data/NotoSans/NotoSans.ttc', 'NotoSans'); + + it('has metadata properties', function() { + assert.equal(font.fullName, 'Noto Sans'); + assert.equal(font.postscriptName, 'NotoSans'); + assert.equal(font.familyName, 'Noto Sans'); + assert.equal(font.subfamilyName, 'Regular'); + assert.equal(font.copyright, 'Copyright 2012 Google Inc. All Rights Reserved.'); + return assert.equal(font.version, 'Version 1.05 uh'); + }); + + it('exposes some metrics', function() { + assert.equal(font.unitsPerEm, 2048); + assert.equal(font.ascent | 0, 2189); + assert.equal(font.descent | 0, -600); + assert.equal(font.lineGap, 0); + assert.equal(font.underlinePosition, -154); + assert.equal(font.underlineThickness, 102); + assert.equal(font.italicAngle, 0); + assert.equal(font.capHeight, 1462); + assert.equal(font.xHeight, 1098); + assert.equal(font.numGlyphs, 8708); + return assert.deepEqual(font.bbox, new BBox(-1268, -600, 2952, 2189)); + }); + + it('exposes tables directly', function() { + let iterable = ['head', 'hhea', 'OS/2', 'post']; + for (let i = 0; i < iterable.length; i++) { + let table = iterable[i]; + assert.equal(typeof font[table], 'object'); + } + }); +}); +|# \ No newline at end of file diff --git a/fontland/fontland/test/subset.rkt b/fontland/fontland/test/subset.rkt new file mode 100755 index 00000000..2e2b782e --- /dev/null +++ b/fontland/fontland/test/subset.rkt @@ -0,0 +1,136 @@ +#lang racket +(require rackunit racket/runtime-path fontland fontland/subset) + +#| +approximates +https://github.com/mbutterick/fontkit/blob/master/test/subset.js +|# + +(define-runtime-path open-sans-ttf "data/OpenSans/OpenSans-Regular.ttf") + +(define font (open-font open-sans-ttf)) + +(test-case + "should create a TTFSubset instance" + (define subset (create-subset font)) + (check-true (ttf-subset? subset))) + +(test-case + "should produce a subset" + (define subset (create-subset font)) + (for ([glyph (in-vector (vector-map glyph-id (glyphrun-glyphs (layout font "hello"))))]) + (subset-add-glyph! subset glyph)) + (define f (encode-to-port subset)) + f + (void)) + +#| + + it('should produce a subset', function(done) { + let subset = font.createSubset(); + for (let glyph of font.glyphsForString('hello')) { + subset.includeGlyph(glyph); + } + + subset.encodeStream().pipe(concat(function(buf) { + let f = fontkit.create(buf); + assert.equal(f.numGlyphs, 5); + assert.equal(f.getGlyph(1).path.toSVG(), font.glyphsForString('h')[0].path.toSVG()); + done(); + })); + }); + + it('should re-encode variation glyphs', function(done) { + if (!fs.existsSync('/Library/Fonts/Skia.ttf')) return done(); + + let font = fontkit.openSync('/Library/Fonts/Skia.ttf', 'Bold'); + let subset = font.createSubset(); + for (let glyph of font.glyphsForString('e')) { + subset.includeGlyph(glyph); + } + + subset.encodeStream().pipe(concat(function(buf) { + let f = fontkit.create(buf); + assert.equal(f.getGlyph(1).path.toSVG(), font.glyphsForString('e')[0].path.toSVG()); + done(); + })); + }); + + it('should handle composite glyphs', function(done) { + let subset = font.createSubset(); + subset.includeGlyph(font.glyphsForString('é')[0]); + + subset.encodeStream().pipe(concat(function(buf) { + let f = fontkit.create(buf); + assert.equal(f.numGlyphs, 4); + assert.equal(f.getGlyph(1).path.toSVG(), font.glyphsForString('é')[0].path.toSVG()); + done(); + })); + }); + + it('should handle fonts with long index to location format (indexToLocFormat = 1)', function(done) { + let font = fontkit.openSync(__dirname + '/data/FiraSans/FiraSans-Regular.ttf'); + let subset = font.createSubset(); + for (let glyph of font.glyphsForString('abcd')) { + subset.includeGlyph(glyph); + } + + subset.encodeStream().pipe(concat(function(buf) { + let f = fontkit.create(buf); + assert.equal(f.numGlyphs, 5); + assert.equal(f.getGlyph(1).path.toSVG(), font.glyphsForString('a')[0].path.toSVG()); + // must test also second glyph which has an odd loca index + assert.equal(f.getGlyph(2).path.toSVG(), font.glyphsForString('b')[0].path.toSVG()); + done(); + })); + }); + }); + + describe('CFF subsetting', function() { + let font = fontkit.openSync(__dirname + '/data/SourceSansPro/SourceSansPro-Regular.otf'); + + it('should create a CFFSubset instance', function() { + let subset = font.createSubset(); + return assert.equal(subset.constructor.name, 'CFFSubset'); + }); + + it('should produce a subset', function(done) { + let subset = font.createSubset(); + let iterable = font.glyphsForString('hello'); + for (let i = 0; i < iterable.length; i++) { + let glyph = iterable[i]; + subset.includeGlyph(glyph); + } + + return subset.encodeStream().pipe(concat(function(buf) { + let stream = new r.DecodeStream(buf); + let cff = new CFFFont(stream); + let glyph = new CFFGlyph(1, [], { stream, 'CFF ': cff }); + assert.equal(glyph.path.toSVG(), font.glyphsForString('h')[0].path.toSVG()); + return done(); + })); + }); + + it('should handle CID fonts', function(done) { + let f = fontkit.openSync(__dirname + '/data/NotoSansCJK/NotoSansCJKkr-Regular.otf'); + let subset = f.createSubset(); + let iterable = f.glyphsForString('갈휸'); + for (let i = 0; i < iterable.length; i++) { + let glyph = iterable[i]; + subset.includeGlyph(glyph); + } + + return subset.encodeStream().pipe(concat(function(buf) { + let stream = new r.DecodeStream(buf); + let cff = new CFFFont(stream); + let glyph = new CFFGlyph(1, [], { stream, 'CFF ': cff }); + assert.equal(glyph.path.toSVG(), f.glyphsForString('갈')[0].path.toSVG()); + assert.equal(cff.topDict.FDArray.length, 2); + assert.deepEqual(cff.topDict.FDSelect.fds, [0, 1, 1]); + return done(); + })); + }); + }); +}); + +|# \ No newline at end of file diff --git a/fontland/fontland/ttf-glyph.rkt b/fontland/fontland/ttf-glyph.rkt new file mode 100644 index 00000000..1e8c70c7 --- /dev/null +++ b/fontland/fontland/ttf-glyph.rkt @@ -0,0 +1,194 @@ +#lang debug racket/base +(require (for-syntax racket/base) + racket/match + racket/list + racket/dict + "struct.rkt" + xenomorph + racket/struct) +(provide (all-defined-out)) + +#| +approximates +https://github.com/mbutterick/fontkit/blob/master/src/glyph/TTFGlyph.js +|# + + +;; The header for both simple and composite glyphs +(define GlyfHeader (x:struct 'numberOfContours int16be ;; if negative, this is a composite glyph + 'xMin int16be + 'yMin int16be + 'xMax int16be + 'yMax int16be)) + +;; Flags for simple glyphs +(define-syntax (define-flag-series stx) + (syntax-case stx () + [(_ . IDS) + #`(match-define (list . IDS) (map (λ (x) (expt 2 x)) (range #,(length (syntax->list #'IDS)))))])) + +;; Flags for simple glyphs +(define-flag-series + ON_CURVE + X_SHORT_VECTOR + Y_SHORT_VECTOR + REPEAT + SAME_X + SAME_Y) + +;; Flags for composite glyphs +(define-flag-series + ARG_1_AND_2_ARE_WORDS + ARGS_ARE_XY_VALUES + ROUND_XY_TO_GRID + WE_HAVE_A_SCALE + __EMPTY-FLAG___ + MORE_COMPONENTS + WE_HAVE_AN_X_AND_Y_SCALE + WE_HAVE_A_TWO_BY_TWO + WE_HAVE_INSTRUCTIONS + USE_MY_METRICS + OVERLAP_COMPOUND + SCALED_COMPONENT_OFFSET + UNSCALED_COMPONENT_OFFSET) + +;; Represents a point in a simple glyph +(struct ttf-glyph-point (on-curve end-contour x y) #:transparent #:mutable) + +(define (+tff-glyph-point on-curve end-contour [x 0] [y 0]) + (ttf-glyph-point on-curve end-contour x y)) + +(define (copy pt) + (apply +tff-glyph-point (struct->list pt))) + +;; Represents a component in a composite glyph +(struct ttf-glyph-component (glyph-id dx dy pos scale-x scale-y scale-01 scale-10) #:transparent #:mutable) + +(define (+ttf-glyph-component glyph-id dx dy + [pos 0] + [scale-x 1] + [scale-y 1] + [scale-01 0] + [scale-10 0]) + (ttf-glyph-component glyph-id dx dy pos scale-x scale-y scale-01 scale-10)) + + +;; Parses just the glyph header and returns the bounding box +#;(define/override (_getCBox internal) + (unfinished)) + +;; Parses a single glyph coordinate +(define (parse-glyph-coord port prev short same) + (unless (input-port? port) + (raise-argument-error 'parse-glyph-coord "input port" port)) + (unless (number? prev) + (raise-argument-error 'parse-glyph-coord "number" prev)) + (unless (and (boolean? short) (boolean? same)) + (raise-argument-error 'parse-glyph-coord "booleans" (list short same))) + (+ prev (if short + ((if (not same) - +) (decode uint8 port)) + (if same 0 (decode int16be port))))) + + +;; Decodes the glyph data into points for simple glyphs, +;; or components for composite glyphs +(require "table-stream.rkt") +(define (glyph-decode ttfg) + (define offsets (hash-ref (get-table (glyph-font ttfg) 'loca) 'offsets)) + (match-define (list glyfPos nextPos) (take (drop offsets (glyph-id ttfg)) 2)) + + ;; Nothing to do if there is no data for this glyph + (and (not (= glyfPos nextPos)) + (let () + (define port (get-table-stream (glyph-font ttfg) 'glyf)) + (pos port (+ (pos port) glyfPos)) + (define startPos (pos port)) + (define glyph-data (decode GlyfHeader port)) + (match (hash-ref glyph-data 'numberOfContours) + [(? positive?) (decode-simple glyph-data port)] + [(? negative?) (decode-composite glyph-data port startPos)]) + glyph-data))) + +(define (decode-simple glyph-data port) + (unless (dict? glyph-data) + (raise-argument-error 'TTFGlyph:decode-simple "decoded RGlyfHeader" glyph-data)) + + (unless (input-port? port) + (raise-argument-error 'TTFGlyph:decode-simple "input port" port)) + + ;; this is a simple glyph + (dict-set! glyph-data 'points empty) + (define endpts-of-contours (decode (x:array #:type uint16be #:length (hash-ref glyph-data 'numberOfContours)) port)) + (dict-set! glyph-data 'instructions (decode (x:array #:type uint8be #:length uint16be) port)) + (define num-coords (add1 (last endpts-of-contours))) + + (define flags + (for*/lists (flag-acc) + ([i (in-naturals)] + #:break (= (length flag-acc) num-coords) + [flag (in-value (decode uint8 port))] + [count (in-range (add1 (if (not (zero? (bitwise-and flag REPEAT))) + (decode uint8 port) + 0)))]) + flag)) + + (match-define-values + (points _ _) + (for/fold ([points empty] [px 0] [py 0]) + ([(flag i) (in-indexed flags)]) + (define point (+tff-glyph-point (zero? (bitwise-and flag ON_CURVE)) (and (index-of endpts-of-contours i) #t) 0 0)) + (define next-px (parse-glyph-coord port px (not (zero? (bitwise-and flag X_SHORT_VECTOR))) (not (zero? (bitwise-and flag SAME_X))))) + (define next-py (parse-glyph-coord port py (not (zero? (bitwise-and flag Y_SHORT_VECTOR))) (not (zero? (bitwise-and flag SAME_Y))))) + (set-ttf-glyph-point-x! point next-px) + (set-ttf-glyph-point-y! point next-py) + (values (cons point points) next-px next-py))) + (dict-set! glyph-data 'points (reverse points))) + + +(define (decode-composite glyph-data port [offset 0]) + ;; this is a composite glyph + (dict-set! glyph-data 'components empty) + (define haveInstructions #f) + (define flags MORE_COMPONENTS) + + (dict-set! glyph-data 'components + (for/list ([i (in-naturals)] + #:break (zero? (bitwise-and flags MORE_COMPONENTS))) + (set! flags (decode uint16be port)) + (define gPos (- (pos port) offset)) + (define glyphID (decode uint16be port)) + (unless haveInstructions + (set! haveInstructions (not (zero? (bitwise-and flags WE_HAVE_INSTRUCTIONS))))) + + (match-define + (list dx dy) + (let ([decoder (if (not (zero? (bitwise-and flags ARG_1_AND_2_ARE_WORDS))) int16be int8)]) + (list (decode decoder port) (decode decoder port)))) + + (define component (+ttf-glyph-component glyphID dx dy)) + (set-ttf-glyph-component-pos! component gPos) + + (cond + [(not (zero? (bitwise-and flags WE_HAVE_A_SCALE))) + (define scale (read-fixed14 port)) + (set-ttf-glyph-component-scale-x! component scale) + (set-ttf-glyph-component-scale-y! component scale)] + [(not (zero? (bitwise-and flags WE_HAVE_AN_X_AND_Y_SCALE))) + (set-ttf-glyph-component-scale-x! component (read-fixed14 port)) + (set-ttf-glyph-component-scale-y! component (read-fixed14 port))] + [(not (zero? (bitwise-and flags WE_HAVE_A_TWO_BY_TWO))) + (set-ttf-glyph-component-scale-x! component (read-fixed14 port)) + (set-ttf-glyph-component-scale-01! component (read-fixed14 port)) + (set-ttf-glyph-component-scale-10! component (read-fixed14 port)) + (set-ttf-glyph-component-scale-y! component (read-fixed14 port))]) + component)) + haveInstructions) + + +(define (bytes->fixed14 b1 b2) + (/ (+ (* b1 (expt 2 8)) b2) (expt 2 14) 1.0)) + +(define (read-fixed14 stream) + (define b1 (decode uint8 stream)) + (define b2 (decode uint8 stream)) + (bytes->fixed14 b1 b2)) \ No newline at end of file diff --git a/fontland/fontland/unsafe/fontconfig.rkt b/fontland/fontland/unsafe/fontconfig.rkt new file mode 100644 index 00000000..cae53bec --- /dev/null +++ b/fontland/fontland/unsafe/fontconfig.rkt @@ -0,0 +1,341 @@ +#lang racket/base +(require ffi/unsafe + ffi/unsafe/define + ffi/unsafe/define/conventions + (for-syntax racket/base + syntax/parse)) + +;; header file +;; https://github.com/servo/libfontconfig/blob/master/fontconfig/fontconfig.h + +;; fontconfig docs +;; https://www.freedesktop.org/software/fontconfig/fontconfig-devel/x102.html + +;; racket-installed fontconfig +(define fc-lib (ffi-lib "libfontconfig" '("1" #f))) + +;; system fontconfig (for opportunistic testing. Not necessarily available) +#;(define fc-lib (ffi-lib "libfontconfig")) + +(define-ffi-definer define-fc fc-lib + #:make-c-id convention:hyphen->camelcase + #:provide provide) + +(define-cpointer-type _FcPattern) +(define-cpointer-type _FcObjectSet) +(define-cpointer-type _FcCharSet) +(define-cpointer-type _FcConfig) +(define-cpointer-type _FcFontSet) +(define-cpointer-type _FcLangSet) +(define-cpointer-type _FcStrSet) +(define-cpointer-type _FcStrList) +(define-cpointer-type _FcRange) +(define-cpointer-type _FcObjectType) +(define-cpointer-type _FcBlanks) +(define-cpointer-type _FcCache) +(define-cpointer-type _FcFileCache) +(define-cpointer-type _FcConstant) +(define-cpointer-type _FcAtomic) + +;; see fontconfig.h for these types and definitions +(define FC-CHARSET-MAP-SIZE (/ 256 32)) + +(define _FcSetName (_enum '(fc-set-system = 0 + fc-set-application))) +(define _FcType (_enum '(fc-type-unknown = -1 + fc-type-void + fc-type-integer + fc-type-double + fc-type-bytes + fc-type-bool + fc-type-matrix + fc-type-char-set + fc-type-ftface + fc-type-lang-set))) +(define _FcResult (_enum '(fc-result-match + fc-result-no-match + fc-result-type-mismatch + fc-result-no-id + fc-result-out-of-memory))) + +(define _FcMatchKind (_enum '(FcMatchPattern + FcMatchFont + FcMatchScan))) + +(define _FcLangResult (_enum '(fc-lang-equal = 0 + fc-lang-different-country + fc-lang-different-territory + fc-lang-different-lang))) + +(define-cstruct _FcMatrix ([xx _double] [xy _double] + [yx _double] [yy _double])) + +(define _FcValue-union (_union _bytes + _int + _bool + _double + _FcMatrix-pointer + _FcCharSet + _FcLangSet)) +(define-cstruct _FcValue ([type _FcType] + [u _FcValue-union])) + + +(define-syntax (define-fc-functions stx) + (syntax-parse stx + [(_ [racket-name:id c-type:expr] ...) + #`(begin (define-fc racket-name c-type) + ...)])) + +(define-fc-functions + [fc-init-load-config (_fun -> _FcConfig)] + [fc-init-load-config-and-fonts (_fun -> _FcConfig)] + [fc-init (_fun -> _bool)] + [fc-fini (_fun -> _void)] + [fc-get-version (_fun -> _int)] + [fc-init-reinitialize (_fun -> _bool)] + [fc-init-bring-upto-date (_fun -> _bool)] + + [fc-pattern-create (_fun -> _FcPattern)] + [fc-pattern-duplicate (_fun _FcPattern -> _FcPattern)] + [fc-pattern-reference (_fun _FcPattern -> _void)] + [fc-pattern-destroy (_fun _FcPattern -> _void)] + [fc-pattern-equal (_fun _FcPattern _FcPattern -> _bool)] + [fc-pattern-equal-subset (_fun _FcPattern _FcPattern _FcObjectSet -> _bool)] + [fc-pattern-filter (_fun _FcPattern _FcObjectSet -> _bool)] + [fc-pattern-hash (_fun _FcPattern -> _int32)] + [fc-pattern-add (_fun _FcPattern _bytes _FcValue _bool -> _bool)] + [fc-pattern-add-weak (_fun _FcPattern _bytes _FcValue _bool -> _bool)] + [fc-pattern-get (_fun _FcPattern + _bytes + _int + [val : (_ptr o _FcValue)] + -> _FcResult + -> val)] + [fc-pattern-get-string (_fun _FcPattern + _bytes + _int + [val : (_ptr o _bytes)] + -> _FcResult + -> val)] + ;; TODO: vararg + ;fc-pattern-build + [fc-pattern-del (_fun _FcPattern _bytes -> _bool)] + [fc-pattern-remove (_fun _FcPattern _bytes _int -> _bool)] + [fc-pattern-print (_fun _FcPattern -> _void)] + [fc-name-parse (_fun _bytes -> _FcPattern/null)] + [fc-name-unparse (_fun _FcPattern -> _bytes)] + [fc-pattern-format (_fun _FcPattern _bytes -> _string)] + + [fc-font-set-create (_fun -> _FcFontSet)] + [fc-font-set-destroy (_fun _FcFontSet -> _void)] + [fc-font-set-add (_fun _FcFontSet _FcPattern -> _bool)] + [fc-font-set-list (_fun _FcConfig/null + [set : (_list i _FcFontSet)] + [_int = (length set)] + _FcPattern + _FcObjectSet + -> + _FcFontSet)] + ;; https://www.freedesktop.org/software/fontconfig/fontconfig-devel/fcfontsetmatch.html + ;; If config is NULL, the current configuration is used + [fc-font-set-match (_fun [_FcConfig/null = #false] + [set : (_list i _FcFontSet)] + [_int = (length set)] + _FcPattern + [res : (_ptr o _FcResult)] + -> + [pat : _FcPattern] + -> + (and (eq? res 'fc-result-match) pat))] + [fc-font-set-print (_fun _FcFontSet -> _void)] + [fc-font-set-sort (_fun _FcConfig/null + [set : (_list i _FcFontSet)] + [_int = (length set)] + _FcPattern + _bool + [cs : (_list o _FcCharSet (length set))] + [res : (_ptr o _FcResult)] + -> + [fs : _FcFontSet] + -> + (values cs res fs))] + + [fc-object-set-create (_fun -> _FcObjectSet)] + [fc-object-set-add (_fun _FcObjectSet _bytes -> _bool)] + [fc-object-set-destroy (_fun _FcObjectSet -> _void)] + ;; TODO: vararg + ;;fc-object-set-build + + ;; TODO: how to do freetype interop? + ;fc-free-type-char-index + ;fc-free-type-char-set + ;fc-free-type-char-set-and-spacing + ;fc-free-type-query + ;fc-free-type-query-face + + [fc-value-destroy (_fun _FcValue -> _void)] + [fc-value-save (_fun _FcValue -> _FcValue)] + [fc-value-print (_fun _FcValue -> _void)] + [fc-value-equal (_fun _FcValue _FcValue -> _bool)] + + [fc-char-set-create (_fun -> _FcCharSet)] + [fc-char-set-destroy (_fun _FcCharSet -> _void)] + [fc-char-set-add-char (_fun _FcCharSet _int -> _void)] + [fc-char-set-del-char (_fun _FcCharSet _int -> _bool)] + [fc-char-set-copy (_fun _FcCharSet -> _FcCharSet)] + [fc-char-set-equal (_fun _FcCharSet _FcCharSet -> _bool)] + [fc-char-set-intersect (_fun _FcCharSet _FcCharSet -> _FcCharSet)] + [fc-char-set-union (_fun _FcCharSet _FcCharSet -> _FcCharSet)] + [fc-char-set-subtract (_fun _FcCharSet _FcCharSet -> _FcCharSet)] + [fc-char-set-merge (_fun _FcCharSet _FcCharSet _bool -> _bool)] + [fc-char-set-has-char (_fun _FcCharSet _int -> _bool)] + [fc-char-set-count (_fun _FcCharSet -> _int)] + [fc-char-set-intersect-count (_fun _FcCharSet _FcCharSet -> _int)] + [fc-char-set-subtract-count (_fun _FcCharSet _FcCharSet -> _int)] + [fc-char-set-is-subset (_fun _FcCharSet _FcCharSet -> _bool)] + [fc-char-set-first-page (_fun _FcCharSet + [map : (_list o _int FC-CHARSET-MAP-SIZE)] + [next : (_ptr io _int)] + -> [res : _int] + -> (values res next map))] + [fc-char-set-next-page (_fun _FcCharSet + [map : (_list o _int FC-CHARSET-MAP-SIZE)] + [next : (_ptr io _int)] + -> [res : _int] + -> (values res next map))] + + [fc-lang-set-create (_fun -> _FcLangSet)] + [fc-lang-set-destroy (_fun _FcLangSet -> _void)] + [fc-lang-set-copy (_fun _FcLangSet -> _FcLangSet)] + [fc-lang-set-add (_fun _FcLangSet _bytes -> _bool)] + [fc-lang-set-del (_fun _FcLangSet _bytes -> _bool)] + [fc-lang-set-union (_fun _FcLangSet _FcLangSet -> _FcLangSet)] + [fc-lang-set-subtract (_fun _FcLangSet _FcLangSet -> _FcLangSet)] + [fc-lang-set-compare (_fun _FcLangSet _FcLangSet -> _FcLangResult)] + [fc-lang-set-contains (_fun _FcLangSet _FcLangSet -> _bool)] + [fc-lang-set-equal (_fun _FcLangSet _FcLangSet -> _bool)] + [fc-lang-set-hash (_fun _FcLangSet -> _int)] + [fc-lang-set-has-lang (_fun _FcLangSet _bytes -> _FcLangResult)] + [fc-get-default-langs (_fun -> _FcStrSet)] + [fc-lang-set-get-langs (_fun _FcLangSet -> _FcStrSet)] + [fc-get-langs (_fun -> _FcStrSet)] + [fc-lang-normalize (_fun _bytes -> _byte)] + [fc-lang-get-char-set (_fun _bytes -> _FcCharSet)] + + [fc-matrix-copy (_fun _FcMatrix-pointer -> _FcMatrix-pointer)] + [fc-matrix-equal (_fun _FcMatrix-pointer _FcMatrix-pointer -> _bool)] + [fc-matrix-multiply (_fun _FcMatrix-pointer _FcMatrix-pointer _FcMatrix-pointer -> _void)] + [fc-matrix-rotate (_fun _FcMatrix-pointer _double _double -> _void)] + [fc-matrix-scale (_fun _FcMatrix-pointer _double _double -> _void)] + [fc-matrix-shear (_fun _FcMatrix-pointer _double _double -> _void)] + + [fc-config-create (_fun -> _FcConfig)] + [fc-config-reference (_fun _FcConfig/null -> _void)] + [fc-config-destroy (_fun _FcConfig -> _void)] + [fc-config-set-current (_fun _FcConfig -> _bool -> (void))] + [fc-config-get-current (_fun -> _FcConfig)] + [fc-config-upto-date (_fun _FcConfig/null -> _bool)] + [fc-config-home (_fun -> _path)] + [fc-config-enable-home (_fun _bool -> _bool)] + [fc-config-build-fonts (_fun _FcConfig/null -> _bool)] + [fc-config-get-config-dirs (_fun _FcConfig/null -> _FcStrList)] + [fc-config-get-font-dirs (_fun _FcConfig/null -> _FcStrList)] + [fc-config-get-config-files (_fun _FcConfig/null -> _FcStrList)] + [fc-config-get-cache-dirs (_fun _FcConfig/null -> _FcStrList)] + [fc-config-get-fonts (_fun _FcConfig/null _FcSetName -> _FcFontSet/null)] + [fc-config-get-blanks (_fun _FcConfig/null -> _FcBlanks/null)] + [fc-config-get-rescan-interval (_fun _FcConfig/null -> _int)] + [fc-config-set-rescan-interval (_fun _FcConfig/null _int -> _bool)] + [fc-config-app-font-add-file (_fun _FcConfig/null _path -> _bool)] + [fc-config-app-font-add-dir (_fun _FcConfig/null _path -> _bool)] + ;; https://www.freedesktop.org/software/fontconfig/fontconfig-devel/fcconfigsubstitute.html + ;; "If config is NULL, the current configuration is used" + [fc-config-substitute (_fun [_FcConfig/null = #false] + _FcPattern + _FcMatchKind + -> _bool)] + [fc-default-substitute (_fun _FcPattern -> _void)] + [fc-config-app-font-clear (_fun _FcConfig/null -> _void)] + + [fc-name-get-object-type (_fun _bytes -> _FcObjectType)] + + [fc-name-get-constant (_fun _bytes -> _FcConstant)] + [fc-name-constant (_fun _bytes + [res : (_ptr o _int)] + -> [b : _bool] + -> (values b res))] + + [fc-blanks-create (_fun -> _FcBlanks/null)] + [fc-blanks-destroy (_fun _FcBlanks -> _void)] + [fc-blanks-add (_fun _FcBlanks _int -> _bool)] + [fc-blanks-is-member (_fun _FcBlanks _int -> _bool)] + + [fc-atomic-create (_fun _bytes -> _FcAtomic)] + [fc-atomic-lock (_fun _FcAtomic -> _bool)] + [fc-atomic-new-file (_fun _FcAtomic -> _bytes)] + [fc-atomic-orig-file (_fun _FcAtomic -> _bytes)] + [fc-atomic-replace-orig (_fun _FcAtomic -> _bool)] + [fc-atomic-delete-new (_fun _FcAtomic -> _void)] + [fc-atomic-unlock (_fun _FcAtomic -> _void)] + [fc-atomic-destroy (_fun _FcAtomic -> _void)] + + [fc-file-scan (_fun [fs : _FcFontSet/null = (fc-font-set-create)] + [_FcStrSet = (fc-str-set-create)] + (_ptr o _FcFileCache) + (_ptr o _FcBlanks/null) + _bytes + [force : _bool = #true] + -> [res : _bool] + -> (and res fs))] + ;; https://www.freedesktop.org/software/fontconfig/fontconfig-devel/fcdirscan.html + ;; "If cache is not zero or if force is FcFalse, this function currently returns FcFalse." + ;; therefore always pass 0 for cache argument + [fc-dir-scan (_fun [fs : _FcFontSet/null = (fc-font-set-create)] + [_FcStrSet = (fc-str-set-create)] + [_int = 0] + (_ptr o _FcBlanks/null) + _bytes + [force : _bool = #true] + -> [res : _bool] + -> (and res fs))] + [fc-file-is-dir (_fun _bytes -> _bool)] + [fc-dir-cache-unlink (_fun _bytes _FcConfig -> _bool)] + [fc-dir-cache-valid (_fun _bytes -> _bool)] + [fc-dir-cache-load (_fun _bytes _FcConfig + [cache-file : (_ptr o _bytes)] + -> [res : _FcCache] + -> (values cache-file res))] + [fc-dir-cache-read (_fun _bytes _bool _FcConfig -> _FcCache)] + ;; TODO: stat struct + ;[fc-dir-cache-load-file (_fun ...)] + [fc-dir-cache-unload (_fun _FcCache -> _void)] + + [fc-cache-dir (_fun _FcCache -> _bytes)] + [fc-cache-copy-set (_fun _FcCache -> _FcFontSet)] + [fc-cache-subdir (_fun _FcCache _int -> _bytes)] + [fc-cache-num-subdir (_fun _FcCache -> _int)] + [fc-cache-num-font (_fun _FcCache -> _int)] + [fc-dir-cache-clean (_fun _bytes _bool -> _bool)] + + [fc-str-set-create (_fun -> _FcStrSet)] + [fc-str-set-member (_fun _FcStrSet _bytes -> _bool)] + [fc-str-set-equal (_fun _FcStrSet _FcStrSet -> _bool)] + [fc-str-set-add (_fun _FcStrSet _bytes -> _bool)] + [fc-str-set-add-filename (_fun _FcStrSet _bytes -> _bool)] + [fc-str-set-del (_fun _FcStrSet _bytes -> _bool)] + [fc-str-set-destroy (_fun _FcStrSet -> _void)] + [fc-str-list-create (_fun _FcStrSet -> _FcStrList)] + [fc-str-list-first (_fun _FcStrList -> _void)] + [fc-str-list-next (_fun _FcStrList -> _bytes)] + [fc-str-list-done (_fun _FcStrList -> _void)] + + ;; TODO: utilities? + ) + +;; defined as macro originally in fontconfig.h +(define (fc-matrix-init mtx) + (set-FcMatrix-xx! 1) + (set-FcMatrix-yy! 1) + (set-FcMatrix-xy! 0) + (set-FcMatrix-yx! 0)) diff --git a/fontland/fontland/unsafe/freetype.rkt b/fontland/fontland/unsafe/freetype.rkt new file mode 100644 index 00000000..9d6c4f04 --- /dev/null +++ b/fontland/fontland/unsafe/freetype.rkt @@ -0,0 +1,479 @@ +#lang debug racket/base +(require ffi/unsafe + ffi/unsafe/define + racket/draw/private/libs) + +(define-syntax-rule (define+provide id val) + (begin + (define id val) + (provide id))) + +(define-runtime-lib freetype-lib + [(unix) (ffi-lib "libfontconfig" '("1" ""))] + [(macosx) (ffi-lib "libfreetype.6.dylib")] + [(windows) (ffi-lib "libfreetype-6.dll")]) + +(define-ffi-definer define-freetype freetype-lib #:provide provide) + +;; types +(define _void-pointer (_cpointer 'void-pointer)) +(define _char _byte) +(define _char-pointer (_cpointer 'char-pointer)) +(define _uchar _ubyte) +(define _FT_Byte _ubyte) +(define _FT_Bytes _bytes) +(define _FT_Char _char) +(define _FT_Int _int) +(define _FT_UInt _uint) +(define _FT_Int16 _short) +(define _FT_UInt16 _ushort) +(define _FT_Int32 _int32) +(define _FT_UInt32 _uint32) +(define _FT_Short _short) +(define _FT_UShort _ushort) +(define _FT_Long _long) +(define _FT_ULong _ulong) +(define _FT_Bool _byte) +(define _FT_Offset _size) ;; equivalent to _size_t? +(define _FT_PtrDist _ptrdiff) ;; equivalent to _longlong? +(define _FT_String _char) +(define _FT_String-pointer (_cpointer 'FT_String-pointer)) ;; char* +(define _FT_Tag _FT_UInt32) +(define _FT_Error _int) +(define _FT_Fixed _long) +(define _FT_Pointer _void-pointer) +(define _FT_Pos _long) +(define _FT_FWord _short) +(define _FT_UFWord _ushort) +(define _FT_F26Dot16 _short) +(define _FT_F26Dot6 _long) +(define _FT_Glyph_Format _int) +(define _FT_Encoding _int) +(define _FT_Generic_Finalizer (_cpointer '_FT_Generic_Finalizer (_fun _void-pointer -> _void))) + +(define _FT_LibraryRec (_cpointer 'FT_LibraryRec)) +(define _FT_Library (_cpointer 'FT_Library)) + +(define-cstruct _FT_Bitmap_Size + ([height _FT_Short] + [width _FT_Short] + [size _FT_Pos] + [x_ppem _FT_Pos] + [y_ppem _FT_Pos])) + +(define-cstruct _FT_CharMapRec + ([face _void-pointer] ; should be FT_Face + [encoding _FT_Encoding] + [platform_id _FT_UShort] + [encoding_id _FT_UShort])) + +(define _FT_CharMap _FT_CharMapRec-pointer) +(define _FT_CharMap-pointer (_cpointer 'FT_CharMap-pointer)) + +(define-cstruct _FT_Generic + ([data _void-pointer] + [finalizer _FT_Generic_Finalizer])) + +(define-cstruct _FT_BBox + ([xMin _FT_Pos] + [yMin _FT_Pos] + [xMax _FT_Pos] + [yMax _FT_Pos])) +(provide (struct-out FT_BBox) + _FT_BBox _FT_BBox-pointer) + +(define-cstruct _FT_Glyph_Metrics + ([width _FT_Pos] + [height _FT_Pos] + [horiBearingX _FT_Pos] + [horiBearingY _FT_Pos] + [horiAdvance _FT_Pos] + [vertBearingX _FT_Pos] + [vertBearingY _FT_Pos] + [vertAdvance _FT_Pos])) +(provide (struct-out FT_Glyph_Metrics) + FT_Glyph_Metrics->list + _FT_Glyph_Metrics _FT_Glyph_Metrics-pointer) + +(define-cstruct _FT_Vector + ([x _FT_Pos] + [y _FT_Pos])) + +(provide (struct-out FT_Vector) + _FT_Vector _FT_Vector-pointer) + +(define-cstruct _FT_Bitmap + ([rows _int] + [width _int] + [pitch _int] + [buffer (_cpointer 'buffer)] + [num_grays _short] + [pixel_mode _ubyte] + [palette_mode _char] + [palette _void-pointer])) + +(define-cstruct _FT_Outline + ([n_contours _short] + [n_points _short] + [points _FT_Vector-pointer] + [tags (_cpointer 'tags)] + [contours (_cpointer 'contours)] + [flags _int])) + +(define-cstruct _FT_GlyphSlotRec + ([library _FT_Library] + [face _void-pointer] + [next _void-pointer] + [reserved _uint] + [generic _FT_Generic] + [metrics _FT_Glyph_Metrics] + [linearHoriAdvance _FT_Fixed] + [linearVertAdvance _FT_Fixed] + [advance _FT_Vector] + [format _FT_Glyph_Format] + [bitmap _FT_Bitmap] + [bitmap_left _FT_Int] + [bitmap_top _FT_Int] + [outline _FT_Outline] + [num_subglyphs _FT_UInt] + [subglyphs _void-pointer] + [control_data _void-pointer] + [control_len _long] + [lsb_delta _FT_Pos] + [rsb_delta _FT_Pos] + [other _void-pointer] + [internal _void-pointer])) + +(define _FT_GlyphSlot _FT_GlyphSlotRec-pointer) + +(provide (struct-out FT_GlyphSlotRec) + _FT_GlyphSlotRec _FT_GlyphSlotRec-pointer) + +(define-cstruct _FT_Size_Metrics + ([x_ppem _FT_UShort] + [y_ppem _FT_UShort] + [x_scale _FT_Fixed] + [y_scale _FT_Fixed] + [ascender _FT_Pos] + [descender _FT_Pos] + [height _FT_Pos] + [max_advance _FT_Pos])) + +(define-cstruct _FT_SizeRec + ([face _void-pointer] + [generic _FT_Generic] + [metrics _FT_Size_Metrics] + [internal _void-pointer])) + +(define _FT_Size _FT_SizeRec-pointer) + +(define-cstruct _FT_FaceRec + ([num_faces _FT_Long] + [face_index _FT_Long] + [face_flag _FT_Long] + [style_flags _FT_Long] + [num_glyphs _FT_Long] + [family_name _string] ; probably _string is a better choice + [style_name _string] + [num_fixed_sizes _FT_Int] + [available_sizes _FT_Bitmap_Size-pointer] + [num_charmaps _FT_Int] + [charmaps _FT_CharMap-pointer] + [generic _FT_Generic] + [bbox _FT_BBox] + [units_per_EM _FT_UShort] + [ascender _FT_Short] + [descender _FT_Short] + [height _FT_Short] + [max_advance_width _FT_Short] + [max_advance_height _FT_Short] + [underline_position _FT_Short] + [underline_thickness _FT_Short] + [glyph _FT_GlyphSlot] + [size _FT_Size] + [charmap _FT_CharMap] + [driver _void-pointer] + [memory _void-pointer] + [stream _void-pointer] + [sizes_list_head _void-pointer] + [sizes_list_tail _void-pointer] + [autohint _FT_Generic] + [extensions _void-pointer] + [internal _void-pointer])) + +(define _FT_Face _FT_FaceRec-pointer) +(provide (struct-out FT_FaceRec) + _FT_FaceRec _FT_FaceRec-pointer _FT_Face) + + +(define _FT_Sfnt_Tag _FT_ULong) + +(define-cstruct _FT_HoriHeader + ([version _FT_Long] + [ascent _FT_Short] + [descent _FT_Short] + [lineGap _FT_Short])) +(provide (struct-out FT_HoriHeader) + _FT_HoriHeader _FT_HoriHeader-pointer) + +(define-cstruct _FT_TT_Postscript + ([FormatType _FT_Fixed] + [italicAngle _FT_Fixed] + [underlinePosition _FT_Short] + [underlineThickness _FT_Short] + [isFixedPitch _FT_ULong] + [minMemType42 _FT_ULong] + [maxMemType42 _FT_ULong] + [minMemType1 _FT_ULong] + [maxMemType1 _FT_ULong])) +(provide (struct-out FT_TT_Postscript) + _FT_TT_Postscript _FT_TT_Postscript-pointer) + +(define-cstruct _FT_panose + ([a _FT_Byte] + [b _FT_Byte] + [c _FT_Byte] + [d _FT_Byte] + [e _FT_Byte] + [f _FT_Byte] + [g _FT_Byte] + [h _FT_Byte] + [i _FT_Byte] + [j _FT_Byte])) + +(define-cstruct _FT_VendID + ([a _FT_Char] + [b _FT_Char] + [c _FT_Char] + [d _FT_Char])) + +(define-cstruct _FT_TT_OS2 + ([version _FT_UShort] + [xAvgCharWidth _FT_Short] + [usWeightClass _FT_UShort] + [usWidthClass _FT_UShort] + [fsType _FT_Short] + [ySubscriptXSize _FT_Short] + [ySubscriptYSize _FT_Short] + [ySubscriptXOffset _FT_Short] + [ySubscriptYOffset _FT_Short] + [ySuperscriptXSize _FT_Short] + [ySuperscriptYSize _FT_Short] + [ySuperscriptXOffset _FT_Short] + [ySuperscriptYOffset _FT_Short] + [yStrikeoutSize _FT_Short] + [yStrikeoutPosition _FT_Short] + [sFamilyClass _FT_Short] + [panose _FT_panose] + [ulUnicodeRange1 _FT_ULong] + [ulUnicodeRange2 _FT_ULong] + [ulUnicodeRange3 _FT_ULong] + [ulUnicodeRange4 _FT_ULong] + [achVendID _FT_VendID] + [fsSelection _FT_UShort] + [usFirstCharIndex _FT_UShort] + [usLastCharIndex _FT_UShort] + [sTypoAscender _FT_Short] + [sTypoDescender _FT_Short] + [sTypoLineGap _FT_Short] + [usWinAscent _FT_UShort] + [usWinDescent _FT_UShort] + [ulCodePageRange1 _FT_ULong] + [ulCodePageRange2 _FT_ULong] + [sxHeight _FT_Short] + [sCapHeight _FT_Short] + [usDefaultChar _FT_UShort] + [usBreakChar _FT_UShort] + [usMaxContext _FT_UShort] + [usLowerOpticalPointSize _FT_UShort] + [usUpperOpticalPointSize _FT_UShort])) +(provide (struct-out FT_TT_OS2) + _FT_TT_OS2 _FT_TT_OS2-pointer) + +(define _full-path + (make-ctype _path + path->complete-path + values)) + +(define-freetype FT_Init_FreeType (_fun (ftl : (_ptr o _FT_Library)) + -> (err : _FT_Error) + -> (if (zero? err) ftl (error 'FT_Init_FreeType)))) + +(define-freetype FT_Set_Char_Size (_fun + _FT_Face + _FT_F26Dot6 + _FT_F26Dot6 + _FT_UInt + _FT_UInt + -> (err : _FT_Error))) + +(define-freetype FT_New_Face (_fun _FT_Library + (path : _full-path) + (_FT_Long = 0) + (ftf : (_ptr o (_or-null _FT_Face))) + -> (err : _FT_Error) + -> (cond + [(zero? err) + ;; see https://www.freetype.org/freetype2/docs/tutorial/step1.html + ;; for meaning of these arguments + (FT_Set_Char_Size ftf 0 (FT_FaceRec-units_per_EM ftf) 0 0) + ftf] + [(= err 1) + (error 'FT_New_Face (format "font ~v not found" path))] + [(error 'FT_New_Face (format "error ~a" err))]))) + + +(define-freetype FT_Done_Face (_fun _FT_Face + -> (err : _FT_Error) + -> (unless (zero? err) (error 'FT_Done_Face (format "error ~a" err))))) + +(define-freetype FT_Done_FreeType (_fun _FT_Library -> (err : _FT_Error) -> (if (zero? err) (void) (error 'FT_Done_FreeType)))) + +(define-freetype FT_Get_Kerning (_fun _FT_Face _FT_UInt _FT_UInt _FT_UInt + (ftv : (_ptr o _FT_Vector)) + -> (err : _FT_Error) + -> (if (zero? err) ftv (error 'FT_Get_Kerning (format "error ~a" err))))) + +(define-freetype FT_Get_Char_Index (_fun _FT_Face _FT_ULong + -> _FT_UInt)) + +(define-freetype FT_Load_Glyph (_fun _FT_Face + _FT_UInt + [_FT_Int32 = FT_LOAD_NO_SCALE] + -> (err : _FT_Error) + -> (unless (zero? err) + (error 'FT_Load_Glyph "failed, try using FT_LOAD_NO_RECURSE or FT_LOAD_NO_SCALE flag instead")))) + +(define-freetype FT_Load_Char (_fun _FT_Face _FT_ULong _FT_Int32 + -> (err : _FT_Error))) + +(define+provide FT_KERNING_UNSCALED 2) +(define+provide FT_LOAD_DEFAULT #x0) +(define+provide FT_LOAD_NO_SCALE (arithmetic-shift 1 0)) +(define+provide FT_LOAD_NO_HINTING (arithmetic-shift 1 1)) +(define+provide FT_LOAD_RENDER (arithmetic-shift 1 2)) +(define+provide FT_LOAD_LINEAR_DESIGN (arithmetic-shift 1 13)) +(define+provide FT_LOAD_NO_RECURSE (arithmetic-shift 1 10)) +(define+provide FT_LOAD_COMPUTE_METRICS (arithmetic-shift 1 21)) + +(define+provide FT_FACE_FLAG_SCALABLE (arithmetic-shift 1 0)) +(define+provide FT_FACE_FLAG_FIXED_SIZES (arithmetic-shift 1 1)) +(define+provide FT_FACE_FLAG_FIXED_WIDTH (arithmetic-shift 1 2)) +(define+provide FT_FACE_FLAG_SFNT (arithmetic-shift 1 3)) +(define+provide FT_FACE_FLAG_HORIZONTAL (arithmetic-shift 1 4)) +(define+provide FT_FACE_FLAG_VERTICAL (arithmetic-shift 1 5)) +(define+provide FT_FACE_FLAG_KERNING (arithmetic-shift 1 6)) +(define+provide FT_FACE_FLAG_FAST_GLYPHS (arithmetic-shift 1 7)) +(define+provide FT_FACE_FLAG_MULTIPLE_MASTERS (arithmetic-shift 1 8)) +(define+provide FT_FACE_FLAG_GLYPH_NAMES (arithmetic-shift 1 9)) +(define+provide FT_FACE_FLAG_EXTERNAL_STREAM (arithmetic-shift 1 10)) +(define+provide FT_FACE_FLAG_HINTER (arithmetic-shift 1 11)) +(define+provide FT_FACE_FLAG_CID_KEYED (arithmetic-shift 1 12)) +(define+provide FT_FACE_FLAG_TRICKY (arithmetic-shift 1 13)) +(define+provide FT_FACE_FLAG_COLOR (arithmetic-shift 1 14)) +(define+provide FT_FACE_FLAG_VARIATION (arithmetic-shift 1 15)) + + +(define-freetype FT_Get_Postscript_Name (_fun _FT_Face -> _string)) + +(define-freetype FT_Load_Sfnt_Table (_fun _FT_Face _FT_Sfnt_Tag _FT_Long + (buffer : (_ptr io _FT_Byte)) + (len : (_ptr io _FT_ULong)) + -> (err : _FT_Error) + -> (and (zero? err) #t))) + + +(define+provide _FT_Gettable_Sfnt_Tag (_enum '(ft_sfnt_head = 0 + ft_sfnt_maxp + ft_sfnt_os2 + ft_sfnt_hhea + ft_sfnt_vhea + ft_sfnt_post + ft_sfnt_pclt))) + +(define-freetype FT_Get_Sfnt_Table (_fun _FT_Face _FT_Gettable_Sfnt_Tag + -> (p : (_cpointer/null 'table-ptr)) + -> (or p (error 'sfnt-table-not-loaded)))) + +(define-freetype FT_Select_Charmap (_fun _FT_Face _FT_Encoding + -> (err : _FT_Error) + -> (unless (zero? err) (error 'FT_Select_Charmap-failed)))) + +(define-freetype FT_Set_Charmap (_fun _FT_Face _FT_CharMapRec + -> (err : _FT_Error) + -> (unless (zero? err) (error 'FT_Set_Charmap-failed)))) + +(provide tag->int) +(define (tag->int tag) + (define signed? #f) + (define big-endian? #t) + (integer-bytes->integer tag signed? big-endian?)) + +(define (int->tag int) + (define signed? #f) + (define big-endian? #t) + (integer->integer-bytes int 4 signed? big-endian?)) + +(module+ test + (require rackunit racket/list) + (define ft-library (FT_Init_FreeType)) + (define (test-face face-str) + (define face (FT_New_Face ft-library face-str)) + (check-equal? (FT_Get_Postscript_Name face) "Charter") + (check-equal? (FT_FaceRec-units_per_EM face) 1000) + (check-true (FT_Load_Sfnt_Table face (tag->int #"cmap") 0 0 0)) + (check-false (FT_Load_Sfnt_Table face (tag->int #"zzap") 0 0 0)) + (check-true (cpointer? (FT_Get_Sfnt_Table face 'ft_sfnt_hhea))) + (define charter-hhea-table (cast (FT_Get_Sfnt_Table face 'ft_sfnt_hhea) _pointer _FT_HoriHeader-pointer)) + (check-equal? (FT_HoriHeader-ascent charter-hhea-table) 980) + (check-equal? (FT_HoriHeader-descent charter-hhea-table) -238) + (check-equal? (FT_HoriHeader-lineGap charter-hhea-table) 0) + (check-equal? + (let ([bbox (FT_FaceRec-bbox face)]) + (list (FT_BBox-xMin bbox) + (FT_BBox-yMin bbox) + (FT_BBox-xMax bbox) + (FT_BBox-yMax bbox))) '(-161 -236 1193 963)) + + (define H-gid (FT_Get_Char_Index face 72)) + (FT_Load_Glyph face H-gid) + ; want bearingX (lsb) and advanceX (advance width) + (define g (FT_FaceRec-glyph face)) + (define metrics (FT_GlyphSlotRec-metrics g)) + #| +see +https://www.freetype.org/freetype2/docs/tutorial/step2.html +"As not all fonts do contain vertical metrics, the values of vertBearingX, vertBearingY and vertAdvance should not be considered reliable if FT_HAS_VERTICAL returns false." +|# + (define horiz-metrics (take (FT_Glyph_Metrics->list metrics) 5)) + (check-equal? horiz-metrics '(672 671 33 671 738)) + (define bearingX (FT_Glyph_Metrics-horiBearingX metrics)) + (check-equal? bearingX 33) + (define advanceX (FT_Glyph_Metrics-horiAdvance metrics)) + (check-equal? advanceX 738) + + (define charter-post-table (cast (FT_Get_Sfnt_Table face 'ft_sfnt_post) _pointer _FT_TT_Postscript-pointer)) + (check-equal? (FT_TT_Postscript-italicAngle charter-post-table) 0) + (check-equal? (FT_TT_Postscript-underlinePosition charter-post-table) -178) ; -207 + 1/2 of thickness = -207 + 29 + (check-equal? (FT_TT_Postscript-underlineThickness charter-post-table) 58) + + (define os2-table (cast (FT_Get_Sfnt_Table face 'ft_sfnt_os2) _pointer _FT_TT_OS2-pointer)) + (check-equal? (FT_TT_OS2-fsType os2-table) #b1000) + (check-equal? (FT_TT_OS2-yStrikeoutSize os2-table) 61) + (check-equal? (FT_TT_OS2-yStrikeoutPosition os2-table) 240) + + (check-equal? (FT_panose->list (FT_TT_OS2-panose os2-table)) '(2 0 5 3 6 0 0 2 0 4)) + + (check-equal? (FT_TT_OS2-sTypoAscender os2-table) 762) + (check-equal? (FT_TT_OS2-sTypoDescender os2-table) -238) + (check-equal? (FT_TT_OS2-sCapHeight os2-table) 671) + (check-equal? (FT_TT_OS2-sxHeight os2-table) 481) + (FT_Done_Face face) + ) + + (test-face "../assets/charter.ttf") + (test-face "../assets/charter.otf") + ) + + + diff --git a/fontland/fontland/unsafe/harfbuzz-helper.rkt b/fontland/fontland/unsafe/harfbuzz-helper.rkt new file mode 100644 index 00000000..ab715b36 --- /dev/null +++ b/fontland/fontland/unsafe/harfbuzz-helper.rkt @@ -0,0 +1,175 @@ +#lang racket/base +(provide (all-defined-out)) + +;; computed enums +;; https://lazka.github.io/pgi-docs/HarfBuzz-0.0/enums.html +;; https://github.com/Robmaister/SharpFont.HarfBuzz/blob/master/Source/SharpFont.HarfBuzz/Script.cs + +(define hb-direction-values + '(HB_DIRECTION_INVALID = 0 + HB_DIRECTION_LTR = 4 + HB_DIRECTION_RTL = 5 + HB_DIRECTION_TTB = 6 + HB_DIRECTION_BTT = 7)) + +(define hb-cluster-levels + '(HB_BUFFER_CLUSTER_LEVEL_MONOTONE_GRAPHEMES = 0 + HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS = 1 + HB_BUFFER_CLUSTER_LEVEL_CHARACTERS = 2)) + +(define (->tag bstr) + (integer-bytes->integer bstr #f #;(= unsigned) 'big-endian)) + +(define hb-script-values + `(HB_SCRIPT_COMMON = ,(->tag #"Zyyy") + HB_SCRIPT_INHERITED = ,(->tag #"Zinh") + HB_SCRIPT_UNKNOWN = ,(->tag #"Zzzz") + HB_SCRIPT_ARABIC = ,(->tag #"Arab") + HB_SCRIPT_ARMENIAN + HB_SCRIPT_BENGALI + HB_SCRIPT_CYRILLIC + HB_SCRIPT_DEVANAGARI + HB_SCRIPT_GEORGIAN + HB_SCRIPT_GREEK + HB_SCRIPT_GUJARATI + HB_SCRIPT_GURMUKHI + HB_SCRIPT_HANGUL + HB_SCRIPT_HAN + HB_SCRIPT_HEBREW + HB_SCRIPT_HIRAGANA + HB_SCRIPT_KANNADA + HB_SCRIPT_KATAKANA + HB_SCRIPT_LAO + HB_SCRIPT_LATIN = ,(->tag #"Latn") + HB_SCRIPT_MALAYALAM + HB_SCRIPT_ORIYA + HB_SCRIPT_TAMIL + HB_SCRIPT_TELUGU + HB_SCRIPT_THAI + HB_SCRIPT_TIBETAN + HB_SCRIPT_BOPOMOFO + HB_SCRIPT_BRAILLE + HB_SCRIPT_CANADIAN_SYLLABICS + HB_SCRIPT_CHEROKEE + HB_SCRIPT_ETHIOPIC + HB_SCRIPT_KHMER + HB_SCRIPT_MONGOLIAN + HB_SCRIPT_MYANMAR + HB_SCRIPT_OGHAM + HB_SCRIPT_RUNIC + HB_SCRIPT_SINHALA + HB_SCRIPT_SYRIAC + HB_SCRIPT_THAANA + HB_SCRIPT_YI + HB_SCRIPT_DESERET + HB_SCRIPT_GOTHIC + HB_SCRIPT_OLD_ITALIC + HB_SCRIPT_BUHID + HB_SCRIPT_HANUNOO + HB_SCRIPT_TAGALOG + HB_SCRIPT_TAGBANWA + HB_SCRIPT_CYPRIOT + HB_SCRIPT_LIMBU + HB_SCRIPT_LINEAR_B + HB_SCRIPT_OSMANYA + HB_SCRIPT_SHAVIAN + HB_SCRIPT_TAI_LE + HB_SCRIPT_UGARITIC + HB_SCRIPT_BUGINESE + HB_SCRIPT_COPTIC + HB_SCRIPT_GLAGOLITIC + HB_SCRIPT_KHAROSHTHI + HB_SCRIPT_NEW_TAI_LUE + HB_SCRIPT_OLD_PERSIAN + HB_SCRIPT_SYLOTI_NAGRI + HB_SCRIPT_TIFINAGH + HB_SCRIPT_BALINESE + HB_SCRIPT_CUNEIFORM + HB_SCRIPT_NKO + HB_SCRIPT_PHAGS_PA + HB_SCRIPT_PHOENICIAN + HB_SCRIPT_CARIAN + HB_SCRIPT_CHAM + HB_SCRIPT_KAYAH_LI + HB_SCRIPT_LEPCHA + HB_SCRIPT_LYCIAN + HB_SCRIPT_LYDIAN + HB_SCRIPT_OL_CHIKI + HB_SCRIPT_REJANG + HB_SCRIPT_SAURASHTRA + HB_SCRIPT_SUNDANESE + HB_SCRIPT_VAI + HB_SCRIPT_AVESTAN + HB_SCRIPT_BAMUM + HB_SCRIPT_EGYPTIAN_HIEROGLYPHS + HB_SCRIPT_IMPERIAL_ARAMAIC + HB_SCRIPT_INSCRIPTIONAL_PAHLAVI + HB_SCRIPT_INSCRIPTIONAL_PARTHIAN + HB_SCRIPT_JAVANESE + HB_SCRIPT_KAITHI + HB_SCRIPT_LISU + HB_SCRIPT_MEETEI_MAYEK + HB_SCRIPT_OLD_SOUTH_ARABIAN + HB_SCRIPT_OLD_TURKIC + HB_SCRIPT_SAMARITAN + HB_SCRIPT_TAI_THAM + HB_SCRIPT_TAI_VIET + HB_SCRIPT_BATAK + HB_SCRIPT_BRAHMI + HB_SCRIPT_MANDAIC + HB_SCRIPT_CHAKMA + HB_SCRIPT_MEROITIC_CURSIVE + HB_SCRIPT_MEROITIC_HIEROGLYPHS + HB_SCRIPT_MIAO + HB_SCRIPT_SHARADA + HB_SCRIPT_SORA_SOMPENG + HB_SCRIPT_TAKRI + HB_SCRIPT_BASSA_VAH + HB_SCRIPT_CAUCASIAN_ALBANIAN + HB_SCRIPT_DUPLOYAN + HB_SCRIPT_ELBASAN + HB_SCRIPT_GRANTHA + HB_SCRIPT_KHOJKI + HB_SCRIPT_KHUDAWADI + HB_SCRIPT_LINEAR_A + HB_SCRIPT_MAHAJANI + HB_SCRIPT_MANICHAEAN + HB_SCRIPT_MENDE_KIKAKUI + HB_SCRIPT_MODI + HB_SCRIPT_MRO + HB_SCRIPT_NABATAEAN + HB_SCRIPT_OLD_NORTH_ARABIAN + HB_SCRIPT_OLD_PERMIC + HB_SCRIPT_PAHAWH_HMONG + HB_SCRIPT_PALMYRENE + HB_SCRIPT_PAU_CIN_HAU + HB_SCRIPT_PSALTER_PAHLAVI + HB_SCRIPT_SIDDHAM + HB_SCRIPT_TIRHUTA + HB_SCRIPT_WARANG_CITI + HB_SCRIPT_AHOM + HB_SCRIPT_ANATOLIAN_HIEROGLYPHS + HB_SCRIPT_HATRAN + HB_SCRIPT_MULTANI + HB_SCRIPT_OLD_HUNGARIAN + HB_SCRIPT_SIGNWRITING + HB_SCRIPT_ADLAM + HB_SCRIPT_BHAIKSUKI + HB_SCRIPT_MARCHEN + HB_SCRIPT_OSAGE + HB_SCRIPT_TANGUT + HB_SCRIPT_NEWA + HB_SCRIPT_MASARAM_GONDI + HB_SCRIPT_NUSHU + HB_SCRIPT_SOYOMBO + HB_SCRIPT_ZANABAZAR_SQUARE + HB_SCRIPT_DOGRA + HB_SCRIPT_GUNJALA_GONDI + HB_SCRIPT_HANIFI_ROHINGYA + HB_SCRIPT_MAKASAR + HB_SCRIPT_MEDEFAIDRIN + HB_SCRIPT_OLD_SOGDIAN + HB_SCRIPT_SOGDIAN + HB_SCRIPT_INVALID = ,(->tag #"\0\0\0\0") + _HB_SCRIPT_MAX_VALUE + _HB_SCRIPT_MAX_VALUE_SIGNED)) \ No newline at end of file diff --git a/fontland/fontland/unsafe/harfbuzz.rkt b/fontland/fontland/unsafe/harfbuzz.rkt new file mode 100644 index 00000000..8dde6d29 --- /dev/null +++ b/fontland/fontland/unsafe/harfbuzz.rkt @@ -0,0 +1,247 @@ +#lang debug racket/base +(require ffi/unsafe + ffi/unsafe/define + racket/draw/private/libs + "freetype.rkt" + "harfbuzz-helper.rkt") +(provide (all-defined-out)) + +(define-runtime-lib harfbuzz-lib + [(unix) (ffi-lib "libharfbuzz" '("0" ""))] + [(macosx) (ffi-lib "libharfbuzz.0.dylib")] + [(windows) (ffi-lib "libharfbuzz-0.dll")]) + +(define-ffi-definer define-harfbuzz harfbuzz-lib #:provide provide) + +;; simple example +;; https://harfbuzz.github.io/ch03s03.html + +;; types +(define _void-pointer (_cpointer 'void-pointer)) +(define _char _byte) +(define _bstring _bytes/nul-terminated) +(define _hb_buffer_t (_cpointer 'hb_buffer_t)) +(define _hb_language_t (_cpointer 'hb_language_t)) +(define _hb_bool_t _int) + +(define-harfbuzz hb_version (_fun (major : (_ptr o _uint)) + (minor : (_ptr o _uint)) + (micro : (_ptr o _uint)) + -> _void + -> (format "~a.~a.~a" major minor micro))) + +(define (default-buffer-setup buf) + (hb_buffer_set_direction buf 'HB_DIRECTION_LTR) + (hb_buffer_set_script buf 'HB_SCRIPT_LATIN) + (hb_buffer_set_cluster_level buf 'HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS) + (hb_buffer_set_language buf (hb_language_from_string #"en")) + buf) + +(define-harfbuzz hb_buffer_create (_fun -> (buf : _hb_buffer_t) + -> (default-buffer-setup buf))) + +;; using `codepoints` will track clusters by codepoints, +;; whereas `utf8` will track clusters by bytes (so high-bytes characters will have bigger clusters) +;; see https://lists.freedesktop.org/archives/harfbuzz/2012-October/002526.html +(define-harfbuzz hb_buffer_add_utf8 (_fun _hb_buffer_t + (text : _string/utf-8) + (text-length : _int = (string-length text)) + (_uint = 0) + (_int = text-length) + -> _void)) +(define-harfbuzz hb_buffer_add_codepoints (_fun _hb_buffer_t + (codepoints : (_list i _hb_codepoint_t)) + (text-length : _int = (length codepoints)) + (_uint = 0) + (_int = text-length) + -> _void)) + +(define _hb_cluster_t (_enum hb-cluster-levels)) +(define-harfbuzz hb_buffer_set_cluster_level (_fun _hb_buffer_t _hb_cluster_t -> _void)) +(define-harfbuzz hb_buffer_get_cluster_level (_fun _hb_buffer_t -> _hb_cluster_t)) + +(define _hb_direction_t (_enum hb-direction-values)) +(define-harfbuzz hb_buffer_set_direction (_fun _hb_buffer_t _hb_direction_t -> _void)) +(define-harfbuzz hb_buffer_get_direction (_fun _hb_buffer_t -> _hb_direction_t)) + +(define _hb_script_t (_enum hb-script-values)) +(define-harfbuzz hb_buffer_set_script (_fun _hb_buffer_t _hb_script_t -> _void)) +(define-harfbuzz hb_buffer_get_script (_fun _hb_buffer_t -> _hb_script_t)) + +(define-harfbuzz hb_language_from_string (_fun _bstring (_int = -1) -> _hb_language_t)) +(define-harfbuzz hb_language_to_string (_fun _hb_language_t -> _bstring)) +(define-harfbuzz hb_buffer_set_language (_fun _hb_buffer_t _hb_language_t -> _void)) +(define-harfbuzz hb_buffer_get_language (_fun _hb_buffer_t -> _hb_language_t)) + +(define _hb_font_t _pointer) +(define _hb_destroy_func_t (_or-null _pointer)) +(define-harfbuzz hb_ft_font_create (_fun _FT_Face + (_hb_destroy_func_t = #false) -> _hb_font_t)) + +(define _hb_tag_t _uint32) + +(define-cstruct _hb_feature_t + ([tag_ _hb_tag_t] + [value _uint32] + [start _uint] + [end _uint])) + +(define _hb_features_t (_or-null _hb_feature_t-pointer)) +(define-harfbuzz hb_shape (_fun _hb_font_t + _hb_buffer_t + (feats : (_list i _hb_feature_t)) + (_uint = (length feats)) + -> _void)) +(define-harfbuzz hb_feature_from_string (_fun (bstr : _bstring) + (_int = (bytes-length bstr)) + (fs : (_ptr o _hb_feature_t)) + -> (success? : _hb_bool_t) + -> (if success? fs (error 'hb_feature_from_string)))) + +#;(define-harfbuzz hb_feature_to_string (_fun _hb_feature_t + (buf : (_ptr o (make-array-type _byte 128))) + (_int = 128) + -> _void)) + +(define _hb_codepoint_t _uint32) +(define _hb_mask_t _uint32) +(define _hb_var_int_t _uint32) ; todo: union type at https://github.com/harfbuzz/harfbuzz/blob/04981ee05d83ed30c9f818106589a4de9c3e9b7f/src/hb-common.h#L96 + +(define-cstruct _hb_glyph_info_t + ([codepoint _hb_codepoint_t] ; holds a glyph id after shaping + [mask _hb_mask_t] + [cluster _uint32] + [var1 _hb_var_int_t] + [var2 _hb_var_int_t])) + +(define-harfbuzz hb_buffer_get_glyph_infos (_fun _hb_buffer_t + (length : (_ptr o _uint)) + -> (res : _hb_glyph_info_t-pointer) + -> (ptr-ref res (_array/list _hb_glyph_info_t length) 0))) + +(define _hb_position_t _int32) + +(define-cstruct _hb_glyph_position_t + ([x_advance _hb_position_t] + [y_advance _hb_position_t] + [x_offset _hb_position_t] + [y_offset _hb_position_t] + [var _hb_var_int_t])) + +(define-harfbuzz hb_buffer_get_glyph_positions (_fun _hb_buffer_t + (length : (_ptr o _uint)) + -> (res : _hb_glyph_position_t-pointer) + -> (ptr-ref res (_array/list _hb_glyph_position_t length) 0))) + +(define-harfbuzz hb_buffer_reset (_fun (buf : _hb_buffer_t) + -> _void + -> (let () + (default-buffer-setup buf) + (void)))) +(define-harfbuzz hb_buffer_clear_contents (_fun (buf : _hb_buffer_t) + -> _void + -> (let () + (default-buffer-setup buf) + (void)))) +(define-harfbuzz hb_buffer_destroy (_fun _hb_buffer_t -> _void)) +(define-harfbuzz hb_font_destroy (_fun _hb_font_t -> _void)) + +(define ft-lib (FT_Init_FreeType)) +(require racket/runtime-path) +(define-runtime-path test-font-path "../assets/fira.ttf") + +(module+ test + (require rackunit) + ;; Create a buffer and put your text in it. + (define buf (hb_buffer_create)) + (define text "Hello World") + #;(hb_buffer_add_utf8 buf text) + (hb_buffer_add_codepoints buf (map char->integer (string->list text))) + + ;; Set the script, language and direction of the buffer. + (check-true (eq? 'HB_DIRECTION_LTR (hb_buffer_get_direction buf))) + (check-true (eq? 'HB_SCRIPT_LATIN (hb_buffer_get_script buf))) + (check-equal? (hb_language_to_string (hb_buffer_get_language buf)) #"en") + + ;; Create a face and a font, using FreeType for now. + (define face (FT_New_Face ft-lib test-font-path)) + (define font (hb_ft_font_create face)) + + ;; Shape! + (hb_shape font buf null) + + ;; Get the glyph and position information. + (define glyph_infos (hb_buffer_get_glyph_infos buf)) + (check-equal? (map hb_glyph_info_t-codepoint glyph_infos) '(111 412 514 514 555 3 296 555 609 514 393)) + + (define glyph_positions (hb_buffer_get_glyph_positions buf)) + (check-equal? (map hb_glyph_position_t-x_advance glyph_positions) '(678 547 291 281 581 268 792 581 383 281 595)) + + ;; Tidy up. + (hb_buffer_destroy buf) + (hb_font_destroy font)) + +(define (make-font path-string) + (define face (FT_New_Face ft-lib path-string)) + (hb_ft_font_create face)) + +(define HB_FEATURE_GLOBAL_START 0) +(define HB_FEATURE_GLOBAL_END 4294967295) +(define (tag->hb-feature bs [val 1]) + (unless (and (bytes? bs) (= (bytes-length bs) 4)) + (raise-argument-error 'tag->hb-feature "4 bytes" bs)) + (make-hb_feature_t (->tag bs) val HB_FEATURE_GLOBAL_START HB_FEATURE_GLOBAL_END)) + +(define liga_on (tag->hb-feature #"liga")) +(define liga_off (tag->hb-feature #"liga" 0)) +(define kern_on (tag->hb-feature #"kern")) +(define kern_off (tag->hb-feature #"kern" 0)) +(define onum_on (tag->hb-feature #"onum")) +(define onum_off (tag->hb-feature #"onum" 0)) + +(define (shape font text [feats null]) + (define buf (hb_buffer_create)) + (hb_buffer_set_direction buf 'HB_DIRECTION_LTR) + (hb_buffer_set_script buf 'HB_SCRIPT_LATIN) + (hb_buffer_set_language buf (hb_language_from_string #"en")) + (hb_buffer_add_utf8 buf text) + (hb_shape font buf feats) + (begin0 + (map cons (map hb_glyph_info_t-codepoint (hb_buffer_get_glyph_infos buf)) + (map hb_glyph_position_t-x_advance (hb_buffer_get_glyph_positions buf))) + (hb_buffer_destroy buf))) + +(require racket/list) +(define (random-string len) + (define chars (map integer->char (range 65 91))) + (list->string (for/list ([i (in-range len)]) + (list-ref chars (random (length chars)))))) + +(module+ test + (define f (make-font test-font-path)) + (define test-str "Tofl 3") + (check-equal? + (shape f test-str) + '((249 . 432) (555 . 581) (732 . 590) (3 . 268) (2017 . 496))) + (check-equal? + (shape f test-str (list kern_off liga_off)) + '((249 . 512) (555 . 581) (450 . 332) (514 . 291) (3 . 268) (2017 . 496))) + (check-equal? + (shape f test-str (list kern_on liga_on onum_on)) + '((249 . 432) (555 . 581) (732 . 590) (3 . 268) (2027 . 487)))) + +(require racket/match sugar/debug racket/string) +(define (wrap str limit) + (define f (make-font test-font-path)) + (for/fold ([lines null] ; listof string + [current-line ""] ; string + #:result (reverse (cons current-line lines))) + ([word (in-list (string-split str " "))]) + (define next-line (string-append current-line " " word)) + (match (shape f next-line) + [(list (cons _ widths) ...) #:when (> (apply + widths) limit) + (values (cons current-line lines) word)] + [_ (values lines next-line)]))) + +(module+ main + (time-avg 10 (wrap "This tutorial provides a brief introduction to the Racket programming language by using one of its picture-drawing libraries. Even if you don’t intend to use Racket for your artistic endeavours, the picture library supports interesting and enlightening examples. This tutorial provides a brief introduction to the Racket programming language by using one of its picture-drawing libraries. Even if you don’t intend to use Racket for your artistic endeavours, the picture library supports interesting and enlightening examples. This tutorial provides a brief introduction to the Racket programming language by using one of its picture-drawing libraries. Even if you don’t intend to use Racket for your artistic endeavours, the picture library supports interesting and enlightening examples." 30000))) diff --git a/fontland/fontland/woff-directory.rkt b/fontland/fontland/woff-directory.rkt new file mode 100644 index 00000000..a2f7e5ce --- /dev/null +++ b/fontland/fontland/woff-directory.rkt @@ -0,0 +1,54 @@ +#lang racket/base +(require xenomorph + "tables.rkt" + "directory.rkt" + sugar/unstable/dict) +(provide woff-directory) + +#| +approximates +https://github.com/mbutterick/fontkit/blob/master/src/tables/WOFFDirectory.js +|# + +(define woff-directory-entry + (x:dict + (dictify + 'tag (x:symbol #:length 4) + 'offset (x:pointer #:type uint32be + #:dest-type 'void + #:relative-to 'global) + 'compLength uint32be + 'length uint32be + 'origChecksum uint32be))) + +(define woff-directory + (x:dict + #:post-decode directory-post-decode + (dictify + 'tag (x:symbol #:length 4) ;should be 'wOFF + 'flavor uint32be + 'length uint32be + 'numTables uint16be + 'reserved (x:reserved #:type uint16be) + 'totalSfntSize uint32be + 'majorVersion uint16be + 'minorVersion uint16be + 'metaOffset uint32be + 'metaLength uint32be + 'metaOrigLength uint32be + 'privOffset uint32be + 'privLength uint32be + 'tables (x:list #:type woff-directory-entry + #:length (λ (p) (hash-ref p 'numTables)))))) + +(module+ test + (require rackunit "helper.rkt" racket/serialize racket/file racket/pretty) + (define ip (open-input-file charter-woff-path)) + (define dir (decode woff-directory ip)) + (check-equal? (hash-ref dir 'tag) 'wOFF) + (define offset (hash-ref (hash-ref (hash-ref dir 'tables) 'head) 'offset)) + (file-position ip offset) + (define name-table (decode head ip)) + (check-equal? (hash-ref name-table 'magicNumber) #x5F0F3CF5) + (check-equal? (hash-ref name-table 'unitsPerEm) 1000) + (check-equal? (hash-ref name-table 'created) (date* 52 12 12 10 7 2013 3 190 #f 0 0 "UTC"))) \ No newline at end of file diff --git a/fontland/fontland/woff2-directory.rkt b/fontland/fontland/woff2-directory.rkt new file mode 100644 index 00000000..63855f26 --- /dev/null +++ b/fontland/fontland/woff2-directory.rkt @@ -0,0 +1,93 @@ +#lang debug racket/base +(require xenomorph + "tables.rkt" + "directory.rkt" + racket/class + racket/match + racket/port + sugar/unstable/dict) +(provide woff2-directory) + +#| +approximates +https://github.com/mbutterick/fontkit/blob/master/src/tables/WOFF2Directory.js +|# + +(define uint32max (sub1 (expt 2 32))) +(define (fits-in-uint32? val) (<= val uint32max)) +(define (times128 x) (arithmetic-shift x 8)) + +(define Base128% + (class x:base% + (super-new) + (define/augment (x:decode port . _) + ;; https://www.w3.org/TR/WOFF2/ + (when (equal? #x80 (peek-byte port)) + (error 'base128-no-leading-zero)) + (for/fold ([res 0]) + ([b (in-input-port-bytes port)] + [count (in-naturals 1)] + #:break (bitwise-bit-set? b 7)) + (when (> count 5) + (error 'base128-longer-than-5-bytes)) + (let ([res (+ (times128 res) (bitwise-and b 127))]) + (unless (fits-in-uint32? res) + (error 'base128-overflow)) + res))) + + (define/augment (x:encode val . _) + (error 'Base128-encode-unimplemented)))) + +(define Base128 (new Base128%)) + +(module+ test + (check-equal? (decode Base128 (bytes #x3f)) 63) + (check-exn exn:fail? (λ () (decode Base128 (bytes #x80)))) ; leading zero + (check-exn exn:fail? (λ () (decode Base128 (bytes #x3f #x3f #x3f #x3f #x3f #x3f)))) ; six bytes + (check-exn exn:fail? (λ () (decode Base128 (bytes 127 127 127 127 127 128)))) ; overflow + (check-equal? (decode Base128 (bytes #x3f 128 #x3f)) 63)) + +(define known-tags '(cmap head hhea hmtx maxp name OS/2 post |cvt | fpgm glyf loca prep |CFF | VORG EBDT EBLC gasp hdmx kern LTSH PCLT VDMX vhea vmtx BASE GDEF GPOS GSUB EBSC JSTF MATH CBDT CBLC COLR CPAL |SVG | sbix acnt avar bdat bloc bsln cvar fdsc feat fmtx fvar gvar hsty just lcar mort morx opbd prop trak Zapf Silf Glat Gloc Feat Sill)) + +(define woff2-directory-entry + (x:dict + (dictify + 'tag (x:symbol #:length 4) + 'offset (x:pointer #:type uint32be + #:dest-type 'void + #:relative-to 'global) + 'compLength uint32be + 'length uint32be + 'origChecksum uint32be))) + +(define woff2-directory + (x:dict + #:post-decode directory-post-decode + (dictify + 'tag (x:symbol #:length 4) ;should be 'wOF2 + 'flavor uint32be + 'length uint32be + 'numTables uint16be + 'reserved (x:reserved #:type uint16be) + 'totalSfntSize uint32be + 'majorVersion uint16be + 'minorVersion uint16be + 'metaOffset uint32be + 'metaLength uint32be + 'metaOrigLength uint32be + 'privOffset uint32be + 'privLength uint32be + 'tables (x:list #:type woff2-directory-entry + #:length (λ (p) (hash-ref p 'numTables)))))) + +(module+ test + (require rackunit "helper.rkt" racket/serialize racket/file racket/pretty) + (define ip (open-input-file charter-woff-path)) + (define dir (decode woff2-directory ip)) + (check-equal? (hash-ref dir 'tag) 'wOFF) + (define offset (hash-ref (hash-ref (hash-ref dir 'tables) 'head) 'offset)) + (file-position ip offset) + (define name-table (decode head ip)) + (check-equal? (hash-ref name-table 'magicNumber) #x5F0F3CF5) + (check-equal? (hash-ref name-table 'unitsPerEm) 1000) + (check-equal? (hash-ref name-table 'created) (date* 52 12 12 10 7 2013 3 190 #f 0 0 "UTC"))) \ No newline at end of file diff --git a/fontland/fontland/zlib.rkt b/fontland/fontland/zlib.rkt new file mode 100644 index 00000000..1a223d74 --- /dev/null +++ b/fontland/fontland/zlib.rkt @@ -0,0 +1,31 @@ +#lang racket/base + +(provide deflate inflate) + +;; see https://groups.google.com/d/topic/racket-users/3CvjHLAmwSQ/discussion +;; for discrepancies between gzip gunzip and zlib + +(require (prefix-in gzip: file/gzip) + (prefix-in gunzip: file/gunzip) png-image) + +(define (deflate bstr) + ;; https://www.ietf.org/rfc/rfc1950.txt + (define rfc-1950-header (bytes #x78 #x9c)) + (define op (open-output-bytes)) + (gzip:deflate (open-input-bytes bstr) op) + (bytes-append rfc-1950-header + (get-output-bytes op) + (integer->integer-bytes (bytes-adler32 bstr) 4 #f 'want-big-endian))) + +(define (inflate bstr) + (define op (open-output-bytes)) + (gunzip:inflate (open-input-bytes (subbytes bstr 2)) op) + (get-output-bytes op)) + +(module+ test + (require rackunit) + (for ([i (in-range 100)]) + (define random-bytes + (apply bytes (for/list ([bidx (in-range 100)]) + (random 256)))) + (check-equal? random-bytes (inflate (deflate random-bytes))))) \ No newline at end of file diff --git a/fontland/info.rkt b/fontland/info.rkt new file mode 100644 index 00000000..33443a93 --- /dev/null +++ b/fontland/info.rkt @@ -0,0 +1,14 @@ +#lang info +(define collection 'multi) +(define version "0.0") +(define deps '("crc32c" + "db-lib" + ["base" #:version "7.1"] + "beautiful-racket-lib" + "debug" + "draw-lib" + "rackunit-lib" + "png-image" + "sugar" + "xenomorph")) +(define update-implies '("xenomorph"))