├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── appveyor.yml ├── binding.gyp ├── index.d.ts ├── package-lock.json ├── package.json ├── src ├── FontDescriptor.h ├── FontManager.cc ├── FontManagerLinux.cc ├── FontManagerMac.mm └── FontManagerWindows.cc ├── test ├── index.js └── mocha.opts └── travis-linux.sh /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | build/ 3 | node_modules/ -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | build/ 3 | node_modules/ 4 | .travis.yml 5 | appveyor.yml 6 | travis-linux.sh 7 | travis-osx.sh 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # setup build matrix 2 | env: 3 | - NODE_VERSION=6 4 | - NODE_VERSION=8 5 | - NODE_VERSION=10 6 | 7 | addons: 8 | apt: 9 | sources: 10 | - ubuntu-toolchain-r-test 11 | packages: 12 | - g++-4.8 13 | 14 | os: 15 | - linux 16 | - osx 17 | 18 | before_install: 19 | # install nvm if it isn't already installed (the mac VMs don't have it) 20 | - if ! command -v nvm &> /dev/null; then curl -s https://raw.githubusercontent.com/creationix/nvm/v0.13.1/install.sh | bash; fi 21 | - . ~/.nvm/nvm.sh 22 | 23 | # install node 24 | - nvm install $NODE_VERSION 25 | - npm config set spin false 26 | - node --version 27 | - npm --version 28 | 29 | install: 30 | # on linux, install some dependencies and fonts used for tests 31 | - if [ "${TRAVIS_OS_NAME}" = "linux" ]; then ./travis-linux.sh; fi 32 | 33 | # Require c++11 support 34 | - if [ "${TRAVIS_OS_NAME}" = "linux" ]; then export CXX=g++-4.8; fi 35 | 36 | # now, actually install the module 37 | - npm install 38 | 39 | script: 40 | - npm test 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2014-present Devon Govett 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/devongovett/font-manager.svg)](https://travis-ci.org/devongovett/font-manager) 2 | 3 | # font-manager 4 | 5 | A C++ module for Node.js providing access to the system font catalog. 6 | 7 | ## Features 8 | 9 | * List all available fonts 10 | * Find fonts with specified characteristics 11 | * Font substitution when characters are missing 12 | 13 | ## Platforms 14 | 15 | * Mac OS X 10.5 and later supported via [CoreText](https://developer.apple.com/library/mac/documentation/Carbon/reference/CoreText_Framework_Ref/_index.html) 16 | * Windows 7 and later supported via [DirectWrite](http://msdn.microsoft.com/en-us/library/windows/desktop/dd368038(v=vs.85).aspx) 17 | * Linux supported via [fontconfig](http://www.freedesktop.org/software/fontconfig) 18 | 19 | ## Installation 20 | 21 | Installation of the `font-manager` module is via npm: 22 | 23 | npm install font-manager 24 | 25 | On Linux, you also may need to install the `libfontconfig-dev` package, for example: 26 | 27 | sudo apt-get install libfontconfig-dev 28 | 29 | ## API 30 | 31 | You load the `font-manager` module using `require` as with all Node modules: 32 | 33 | ```javascript 34 | var fontManager = require('font-manager'); 35 | ``` 36 | 37 | All of the methods exported by `font-manager` have both synchronous and asynchronous versions available. 38 | You should generally prefer the asynchronous version as it will allow your program to continue doing other 39 | processing while a request for fonts is processing in the background, which may be expensive depending on 40 | the platform APIs that are available. 41 | 42 | * [`getAvailableFonts()`](#getavailablefonts) 43 | * [`findFonts(fontDescriptor)`](#findfontsfontdescriptor) 44 | * [`findFont(fontDescriptor)`](#findfontfontdescriptor) 45 | * [`substituteFont(postscriptName, text)`](#substitutefontpostscriptname-text) 46 | 47 | ### getAvailableFonts() 48 | 49 | Returns an array of all [font descriptors](#font-descriptor) available on the system. 50 | 51 | ```javascript 52 | // asynchronous API 53 | fontManager.getAvailableFonts(function(fonts) { ... }); 54 | 55 | // synchronous API 56 | var fonts = fontManager.getAvailableFontsSync(); 57 | 58 | // output 59 | [ { path: '/Library/Fonts/Arial.ttf', 60 | postscriptName: 'ArialMT', 61 | family: 'Arial', 62 | style: 'Regular', 63 | weight: 400, 64 | width: 5, 65 | italic: false, 66 | monospace: false }, 67 | ... ] 68 | ``` 69 | 70 | ### findFonts(fontDescriptor) 71 | 72 | Returns an array of [font descriptors](#font-descriptor) matching a query 73 | [font descriptor](#font-descriptor). 74 | The returned array may be empty if no fonts match the font descriptor. 75 | 76 | ```javascript 77 | // asynchronous API 78 | fontManager.findFonts({ family: 'Arial' }, function(fonts) { ... }); 79 | 80 | // synchronous API 81 | var fonts = fontManager.findFontsSync({ family: 'Arial' }); 82 | 83 | // output 84 | [ { path: '/Library/Fonts/Arial.ttf', 85 | postscriptName: 'ArialMT', 86 | family: 'Arial', 87 | style: 'Regular', 88 | weight: 400, 89 | width: 5, 90 | italic: false, 91 | monospace: false }, 92 | { path: '/Library/Fonts/Arial Bold.ttf', 93 | postscriptName: 'Arial-BoldMT', 94 | family: 'Arial', 95 | style: 'Bold', 96 | weight: 700, 97 | width: 5, 98 | italic: false, 99 | monospace: false } ] 100 | ``` 101 | 102 | ### findFont(fontDescriptor) 103 | 104 | Returns a single [font descriptors](#font-descriptor) matching a query 105 | [font descriptors](#font-descriptor) as well as possible. This method 106 | always returns a result (never `null`), so sometimes the output will not 107 | exactly match the input font descriptor if not all input parameters 108 | could be met. 109 | 110 | ```javascript 111 | // asynchronous API 112 | fontManager.findFont({ family: 'Arial', weight: 700 }, function(font) { ... }); 113 | 114 | // synchronous API 115 | var font = fontManager.findFontSync({ family: 'Arial', weight: 700 }); 116 | 117 | // output 118 | { path: '/Library/Fonts/Arial Bold.ttf', 119 | postscriptName: 'Arial-BoldMT', 120 | family: 'Arial', 121 | style: 'Bold', 122 | weight: 700, 123 | width: 5, 124 | italic: false, 125 | monospace: false } 126 | ``` 127 | 128 | ### substituteFont(postscriptName, text) 129 | 130 | Substitutes the font with the given `postscriptName` with another font 131 | that contains the characters in `text`. If a font matching `postscriptName` 132 | is not found, a font containing the given characters is still returned. If 133 | a font matching `postscriptName` *is* found, its characteristics (bold, italic, etc.) 134 | are used to find a suitable replacement. If the font already contains the characters 135 | in `text`, it is not replaced and the font descriptor for the original font is returned. 136 | 137 | ```javascript 138 | // asynchronous API 139 | fontManager.substituteFont('TimesNewRomanPSMT', '汉字', function(font) { ... }); 140 | 141 | // synchronous API 142 | var font = fontManager.substituteFontSync('TimesNewRomanPSMT', '汉字'); 143 | 144 | // output 145 | { path: '/Library/Fonts/Songti.ttc', 146 | postscriptName: 'STSongti-SC-Regular', 147 | family: 'Songti SC', 148 | style: 'Regular', 149 | weight: 400, 150 | width: 5, 151 | italic: false, 152 | monospace: false } 153 | ``` 154 | 155 | ### Font Descriptor 156 | 157 | Font descriptors are normal JavaScript objects that describe characteristics of 158 | a font. They are passed to the `findFonts` and `findFont` methods and returned by 159 | all of the methods. Any combination of the fields documented below can be used to 160 | find fonts, but all methods return full font descriptors. 161 | 162 | Name | Type | Description 163 | ---------------- | ------- | ----------- 164 | `path` | string | The path to the font file in the filesystem. **(not applicable for queries, only for results)** 165 | `postscriptName` | string | The PostScript name of the font (e.g `'Arial-BoldMT'`). This uniquely identities a font in most cases. 166 | `family` | string | The font family name (e.g `'Arial'`) 167 | `style` | string | The font style name (e.g. `'Bold'`) 168 | `weight` | number | The font weight (e.g. `400` for normal weight). Should be a multiple of 100, between 100 and 900. See [below](#weights) for weight documentation. 169 | `width` | number | The font width (e.g. `5` for normal width). Should be an integer between 1 and 9. See [below](#widths) for width documentation. 170 | `italic` | boolean | Whether the font is italic or not. 171 | `monospace` | boolean | Whether the font is monospace or not. 172 | 173 | #### Weights 174 | 175 | Value | Name 176 | ----- | ------------------------- 177 | 100 | Thin 178 | 200 | Ultra Light 179 | 300 | Light 180 | 400 | Normal 181 | 500 | Medium 182 | 600 | Semi Bold 183 | 700 | Bold 184 | 800 | Ultra Bold 185 | 900 | Heavy 186 | 187 | #### Widths 188 | 189 | Value | Name 190 | ----- | ----------------------------- 191 | 1 | Ultra Condensed 192 | 2 | Extra Condensed 193 | 3 | Condensed 194 | 4 | Semi Condensed 195 | 5 | Normal 196 | 6 | Semi Expanded 197 | 7 | Expanded 198 | 8 | Extra Expanded 199 | 9 | Ultra Expanded 200 | 201 | ## License 202 | 203 | MIT 204 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | os: Windows Server 2012 2 | environment: 3 | VisualStudioVersion: 11.0 4 | matrix: 5 | - nodejs_version: "6" 6 | - nodejs_version: "8" 7 | - nodejs_version: "10" 8 | 9 | install: 10 | - ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version) 11 | - npm install 12 | 13 | test_script: 14 | - node --version 15 | - npm --version 16 | - ps: "npm test # PowerShell" # Pass comment to PS for easier debugging 17 | - cmd: npm test 18 | 19 | build: off 20 | version: "{build}" 21 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "fontmanager", 5 | "sources": [ "src/FontManager.cc" ], 6 | "include_dirs" : [ 7 | " { ... }); 39 | */ 40 | export function getAvailableFonts(callback: (fonts: FontDescriptor[]) => void): void; 41 | 42 | /** 43 | * Queries all the fonts in the system matching the given parameters 44 | * 45 | * @param fontDescriptor Query parameters 46 | * @example 47 | * findFontsSync({ family: 'Arial' }); 48 | * findFontsSync(); 49 | * @returns All fonts descriptors matching query parameters 50 | */ 51 | export function findFontsSync(fontDescriptor: QueryFontDescriptor | undefined): FontDescriptor[]; 52 | 53 | /** 54 | * Queries all the fonts in the system matching the given parameters 55 | * 56 | * @param fontDescriptor Query parameters 57 | * @param callback Contains the font data 58 | * @example 59 | * findFonts({ family: 'Arial' }, (fonts) => { ... }); 60 | * findFonts((fonts) => { ... }); 61 | */ 62 | export function findFonts(fontDescriptor: QueryFontDescriptor | undefined, callback: (fonts: FontDescriptor[]) => void); 63 | 64 | /** 65 | * Find only one font matching the given query. This function always returns 66 | * a result (never null), so sometimes the output will not exactly match the 67 | * input font descriptor if not all input parameters could be met 68 | * 69 | * @param fontDescriptor Query parameters 70 | * @example 71 | * findFontSync({ family: 'Arial', weight: 700 }); 72 | * findFontSync(); 73 | * @returns Only one font description matching those query parameters 74 | */ 75 | export function findFontSync(fontDescriptor: QueryFontDescriptor): FontDescriptor; 76 | 77 | /** 78 | * Find only one font matching the given query. This function always returns 79 | * a result (never null), so sometimes the output will not exactly match the 80 | * input font descriptor if not all input parameters could be met 81 | * 82 | * @param fontDescriptor Query parameters 83 | * @example 84 | * findFont({ family: 'Arial', weight: 700 }, (font) => { ... }); 85 | * findFont((font) => { ... }); 86 | * @returns Only one font description matching those query parameters 87 | */ 88 | export function findFont(fontDescriptor: QueryFontDescriptor | undefined, callback: (font: FontDescriptor) => void); 89 | 90 | /** 91 | * Substitutes the font with the given post script name with another font 92 | * that contains the characters in text. If a font matching post script 93 | * name is not found, a fount containing the given characters is still 94 | * returned. If a font matching post script name is found, its 95 | * characteristics (bold, italic, etc) are used to find a suitable 96 | * replacement. If the font already contains the characters in text, it is 97 | * not replaced and the font descriptor for the original font is returned 98 | * 99 | * @param postscriptName Name of the font to be replaced 100 | * @param text Characters for matching 101 | * @returns Only one font description matching the function description 102 | */ 103 | export function substituteFontSync(postscriptName: string, text: string): FontDescriptor; 104 | 105 | /** 106 | * Substitutes the font with the given post script name with another font 107 | * that contains the characters in text. If a font matching post script 108 | * name is not found, a fount containing the given characters is still 109 | * returned. If a font matching post script name is found, its 110 | * characteristics (bold, italic, etc) are used to find a suitable 111 | * replacement. If the font already contains the characters in text, it is 112 | * not replaced and the font descriptor for the original font is returned 113 | * 114 | * @param postscriptName Name of the font to be replaced 115 | * @param text Characters for matching 116 | */ 117 | export function substituteFont(postscriptName: string, text: string, callback: (font: FontDescriptor) => void); 118 | } 119 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "font-manager", 3 | "version": "0.3.1", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "balanced-match": { 8 | "version": "1.0.0", 9 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 10 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 11 | "dev": true 12 | }, 13 | "brace-expansion": { 14 | "version": "1.1.11", 15 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 16 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 17 | "dev": true, 18 | "requires": { 19 | "balanced-match": "^1.0.0", 20 | "concat-map": "0.0.1" 21 | } 22 | }, 23 | "browser-stdout": { 24 | "version": "1.3.1", 25 | "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", 26 | "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", 27 | "dev": true 28 | }, 29 | "commander": { 30 | "version": "2.15.1", 31 | "resolved": "http://registry.npmjs.org/commander/-/commander-2.15.1.tgz", 32 | "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", 33 | "dev": true 34 | }, 35 | "concat-map": { 36 | "version": "0.0.1", 37 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 38 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 39 | "dev": true 40 | }, 41 | "debug": { 42 | "version": "3.1.0", 43 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 44 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 45 | "dev": true, 46 | "requires": { 47 | "ms": "2.0.0" 48 | } 49 | }, 50 | "diff": { 51 | "version": "3.5.0", 52 | "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", 53 | "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", 54 | "dev": true 55 | }, 56 | "escape-string-regexp": { 57 | "version": "1.0.5", 58 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 59 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 60 | "dev": true 61 | }, 62 | "fs.realpath": { 63 | "version": "1.0.0", 64 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 65 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 66 | "dev": true 67 | }, 68 | "glob": { 69 | "version": "7.1.2", 70 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", 71 | "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", 72 | "dev": true, 73 | "requires": { 74 | "fs.realpath": "^1.0.0", 75 | "inflight": "^1.0.4", 76 | "inherits": "2", 77 | "minimatch": "^3.0.4", 78 | "once": "^1.3.0", 79 | "path-is-absolute": "^1.0.0" 80 | } 81 | }, 82 | "growl": { 83 | "version": "1.10.5", 84 | "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", 85 | "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", 86 | "dev": true 87 | }, 88 | "has-flag": { 89 | "version": "3.0.0", 90 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 91 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", 92 | "dev": true 93 | }, 94 | "he": { 95 | "version": "1.1.1", 96 | "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", 97 | "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", 98 | "dev": true 99 | }, 100 | "inflight": { 101 | "version": "1.0.6", 102 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 103 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 104 | "dev": true, 105 | "requires": { 106 | "once": "^1.3.0", 107 | "wrappy": "1" 108 | } 109 | }, 110 | "inherits": { 111 | "version": "2.0.3", 112 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 113 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 114 | "dev": true 115 | }, 116 | "minimatch": { 117 | "version": "3.0.4", 118 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 119 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 120 | "dev": true, 121 | "requires": { 122 | "brace-expansion": "^1.1.7" 123 | } 124 | }, 125 | "minimist": { 126 | "version": "0.0.8", 127 | "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 128 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", 129 | "dev": true 130 | }, 131 | "mkdirp": { 132 | "version": "0.5.1", 133 | "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 134 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 135 | "dev": true, 136 | "requires": { 137 | "minimist": "0.0.8" 138 | } 139 | }, 140 | "mocha": { 141 | "version": "5.2.0", 142 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", 143 | "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", 144 | "dev": true, 145 | "requires": { 146 | "browser-stdout": "1.3.1", 147 | "commander": "2.15.1", 148 | "debug": "3.1.0", 149 | "diff": "3.5.0", 150 | "escape-string-regexp": "1.0.5", 151 | "glob": "7.1.2", 152 | "growl": "1.10.5", 153 | "he": "1.1.1", 154 | "minimatch": "3.0.4", 155 | "mkdirp": "0.5.1", 156 | "supports-color": "5.4.0" 157 | } 158 | }, 159 | "ms": { 160 | "version": "2.0.0", 161 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 162 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", 163 | "dev": true 164 | }, 165 | "nan": { 166 | "version": "2.11.1", 167 | "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.1.tgz", 168 | "integrity": "sha512-iji6k87OSXa0CcrLl9z+ZiYSuR2o+c0bGuNmXdrhTQTakxytAFsC56SArGYoiHlJlFoHSnvmhpceZJaXkVuOtA==" 169 | }, 170 | "once": { 171 | "version": "1.4.0", 172 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 173 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 174 | "dev": true, 175 | "requires": { 176 | "wrappy": "1" 177 | } 178 | }, 179 | "path-is-absolute": { 180 | "version": "1.0.1", 181 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 182 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 183 | "dev": true 184 | }, 185 | "supports-color": { 186 | "version": "5.4.0", 187 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", 188 | "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", 189 | "dev": true, 190 | "requires": { 191 | "has-flag": "^3.0.0" 192 | } 193 | }, 194 | "wrappy": { 195 | "version": "1.0.2", 196 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 197 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 198 | "dev": true 199 | } 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "font-manager", 3 | "version": "0.3.1", 4 | "description": "Provides access to the system font catalog", 5 | "main": "build/Release/fontmanager", 6 | "types": "index.d.ts", 7 | "dependencies": { 8 | "nan": ">=2.10.0" 9 | }, 10 | "devDependencies": { 11 | "mocha": "*" 12 | }, 13 | "directories": { 14 | "test": "test" 15 | }, 16 | "scripts": { 17 | "test": "mocha" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/devongovett/font-manager.git" 22 | }, 23 | "keywords": [ 24 | "font", 25 | "find", 26 | "search", 27 | "substitute", 28 | "enumerate" 29 | ], 30 | "author": "Devon Govett ", 31 | "license": "MIT", 32 | "gypfile": true, 33 | "bugs": { 34 | "url": "https://github.com/devongovett/font-manager/issues" 35 | }, 36 | "homepage": "https://github.com/devongovett/font-manager" 37 | } 38 | -------------------------------------------------------------------------------- /src/FontDescriptor.h: -------------------------------------------------------------------------------- 1 | #ifndef FONT_DESCRIPTOR_H 2 | #define FONT_DESCRIPTOR_H 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace v8; 11 | 12 | enum FontWeight { 13 | FontWeightUndefined = 0, 14 | FontWeightThin = 100, 15 | FontWeightUltraLight = 200, 16 | FontWeightLight = 300, 17 | FontWeightNormal = 400, 18 | FontWeightMedium = 500, 19 | FontWeightSemiBold = 600, 20 | FontWeightBold = 700, 21 | FontWeightUltraBold = 800, 22 | FontWeightHeavy = 900 23 | }; 24 | 25 | enum FontWidth { 26 | FontWidthUndefined = 0, 27 | FontWidthUltraCondensed = 1, 28 | FontWidthExtraCondensed = 2, 29 | FontWidthCondensed = 3, 30 | FontWidthSemiCondensed = 4, 31 | FontWidthNormal = 5, 32 | FontWidthSemiExpanded = 6, 33 | FontWidthExpanded = 7, 34 | FontWidthExtraExpanded = 8, 35 | FontWidthUltraExpanded = 9 36 | }; 37 | 38 | struct FontDescriptor { 39 | public: 40 | const char *path; 41 | const char *postscriptName; 42 | const char *family; 43 | const char *style; 44 | FontWeight weight; 45 | FontWidth width; 46 | bool italic; 47 | bool monospace; 48 | 49 | FontDescriptor(Local obj) { 50 | path = NULL; 51 | postscriptName = getString(obj, "postscriptName"); 52 | family = getString(obj, "family"); 53 | style = getString(obj, "style"); 54 | weight = (FontWeight) getNumber(obj, "weight"); 55 | width = (FontWidth) getNumber(obj, "width"); 56 | italic = getBool(obj, "italic"); 57 | monospace = getBool(obj, "monospace"); 58 | } 59 | 60 | FontDescriptor() { 61 | path = NULL; 62 | postscriptName = NULL; 63 | family = NULL; 64 | style = NULL; 65 | weight = FontWeightUndefined; 66 | width = FontWidthUndefined; 67 | italic = false; 68 | monospace = false; 69 | } 70 | 71 | FontDescriptor(const char *path, const char *postscriptName, const char *family, const char *style, 72 | FontWeight weight, FontWidth width, bool italic, bool monospace) { 73 | this->path = copyString(path); 74 | this->postscriptName = copyString(postscriptName); 75 | this->family = copyString(family); 76 | this->style = copyString(style); 77 | this->weight = weight; 78 | this->width = width; 79 | this->italic = italic; 80 | this->monospace = monospace; 81 | } 82 | 83 | FontDescriptor(FontDescriptor *desc) { 84 | path = copyString(desc->path); 85 | postscriptName = copyString(desc->postscriptName); 86 | family = copyString(desc->family); 87 | style = copyString(desc->style); 88 | weight = desc->weight; 89 | width = desc->width; 90 | italic = desc->italic; 91 | monospace = desc->monospace; 92 | } 93 | 94 | ~FontDescriptor() { 95 | if (path) 96 | delete path; 97 | 98 | if (postscriptName) 99 | delete postscriptName; 100 | 101 | if (family) 102 | delete family; 103 | 104 | if (style) 105 | delete style; 106 | 107 | postscriptName = NULL; 108 | family = NULL; 109 | style = NULL; 110 | } 111 | 112 | Local toJSObject() { 113 | Nan::EscapableHandleScope scope; 114 | Local res = Nan::New(); 115 | 116 | if (path) { 117 | Nan::Set(res, Nan::New("path").ToLocalChecked(), Nan::New(path).ToLocalChecked()); 118 | } 119 | 120 | if (postscriptName) { 121 | Nan::Set(res, Nan::New("postscriptName").ToLocalChecked(), Nan::New(postscriptName).ToLocalChecked()); 122 | } 123 | 124 | if (family) { 125 | Nan::Set(res, Nan::New("family").ToLocalChecked(), Nan::New(family).ToLocalChecked()); 126 | } 127 | 128 | if (style) { 129 | Nan::Set(res, Nan::New("style").ToLocalChecked(), Nan::New(style).ToLocalChecked()); 130 | } 131 | 132 | Nan::Set(res, Nan::New("weight").ToLocalChecked(), Nan::New(weight)); 133 | Nan::Set(res, Nan::New("width").ToLocalChecked(), Nan::New(width)); 134 | Nan::Set(res, Nan::New("italic").ToLocalChecked(), Nan::New(italic)); 135 | Nan::Set(res, Nan::New("monospace").ToLocalChecked(), Nan::New(monospace)); 136 | return scope.Escape(res); 137 | } 138 | 139 | private: 140 | char *copyString(const char *input) { 141 | if (!input) 142 | return NULL; 143 | 144 | char *str = new char[strlen(input) + 1]; 145 | strcpy(str, input); 146 | return str; 147 | } 148 | 149 | char *getString(Local obj, const char *name) { 150 | Nan::HandleScope scope; 151 | MaybeLocal value = Nan::Get(obj, Nan::New(name).ToLocalChecked()); 152 | 153 | if (!value.IsEmpty() && value.ToLocalChecked()->IsString()) { 154 | return copyString(*Nan::Utf8String(value.ToLocalChecked())); 155 | } 156 | 157 | return NULL; 158 | } 159 | 160 | int getNumber(Local obj, const char *name) { 161 | Nan::HandleScope scope; 162 | MaybeLocal value = Nan::Get(obj, Nan::New(name).ToLocalChecked()); 163 | 164 | if (!value.IsEmpty() && value.ToLocalChecked()->IsNumber()) { 165 | return value.ToLocalChecked()->Int32Value(Nan::GetCurrentContext()).FromJust(); 166 | } 167 | 168 | return 0; 169 | } 170 | 171 | bool getBool(Local obj, const char *name) { 172 | Nan::HandleScope scope; 173 | MaybeLocal value = Nan::Get(obj, Nan::New(name).ToLocalChecked()); 174 | 175 | if (!value.IsEmpty() && value.ToLocalChecked()->IsBoolean()) { 176 | return value.ToLocalChecked()->BooleanValue(Nan::GetCurrentContext()).FromJust(); 177 | } 178 | 179 | return false; 180 | } 181 | }; 182 | 183 | class ResultSet : public std::vector { 184 | public: 185 | ~ResultSet() { 186 | for (ResultSet::iterator it = this->begin(); it != this->end(); it++) { 187 | delete *it; 188 | } 189 | } 190 | }; 191 | 192 | #endif 193 | -------------------------------------------------------------------------------- /src/FontManager.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "FontDescriptor.h" 7 | 8 | using namespace v8; 9 | 10 | // these functions are implemented by the platform 11 | ResultSet *getAvailableFonts(); 12 | ResultSet *findFonts(FontDescriptor *); 13 | FontDescriptor *findFont(FontDescriptor *); 14 | FontDescriptor *substituteFont(char *, char *); 15 | 16 | // converts a ResultSet to a JavaScript array 17 | Local collectResults(ResultSet *results) { 18 | Nan::EscapableHandleScope scope; 19 | Local res = Nan::New(results->size()); 20 | 21 | int i = 0; 22 | for (ResultSet::iterator it = results->begin(); it != results->end(); it++) { 23 | Nan::Set(res, i++, (*it)->toJSObject()); 24 | } 25 | 26 | delete results; 27 | return scope.Escape(res); 28 | } 29 | 30 | // converts a FontDescriptor to a JavaScript object 31 | Local wrapResult(FontDescriptor *result) { 32 | Nan::EscapableHandleScope scope; 33 | if (result == NULL) 34 | return scope.Escape(Nan::Null()); 35 | 36 | Local res = result->toJSObject(); 37 | delete result; 38 | return scope.Escape(res); 39 | } 40 | 41 | // holds data about an operation that will be 42 | // performed on a background thread 43 | struct AsyncRequest { 44 | uv_work_t work; 45 | FontDescriptor *desc; // used by findFont and findFonts 46 | char *postscriptName; // used by substituteFont 47 | char *substitutionString; // ditto 48 | FontDescriptor *result; // for functions with a single result 49 | ResultSet *results; // for functions with multiple results 50 | Nan::Callback *callback; // the actual JS callback to call when we are done 51 | 52 | AsyncRequest(Local v) { 53 | work.data = (void *)this; 54 | callback = new Nan::Callback(v.As()); 55 | desc = NULL; 56 | postscriptName = NULL; 57 | substitutionString = NULL; 58 | result = NULL; 59 | results = NULL; 60 | } 61 | 62 | ~AsyncRequest() { 63 | delete callback; 64 | 65 | if (desc) 66 | delete desc; 67 | 68 | if (postscriptName) 69 | delete postscriptName; 70 | 71 | if (substitutionString) 72 | delete substitutionString; 73 | 74 | // result/results deleted by wrapResult/collectResults respectively 75 | } 76 | }; 77 | 78 | // calls the JavaScript callback for a request 79 | void asyncCallback(uv_work_t *work) { 80 | Nan::HandleScope scope; 81 | AsyncRequest *req = (AsyncRequest *) work->data; 82 | Nan::AsyncResource async("asyncCallback"); 83 | Local info[1]; 84 | 85 | if (req->results) { 86 | info[0] = collectResults(req->results); 87 | } else if (req->result) { 88 | info[0] = wrapResult(req->result); 89 | } else { 90 | info[0] = Nan::Null(); 91 | } 92 | 93 | req->callback->Call(1, info, &async); 94 | delete req; 95 | } 96 | 97 | void getAvailableFontsAsync(uv_work_t *work) { 98 | AsyncRequest *req = (AsyncRequest *) work->data; 99 | req->results = getAvailableFonts(); 100 | } 101 | 102 | template 103 | NAN_METHOD(getAvailableFonts) { 104 | if (async) { 105 | if (info.Length() < 1 || !info[0]->IsFunction()) 106 | return Nan::ThrowTypeError("Expected a callback"); 107 | 108 | AsyncRequest *req = new AsyncRequest(info[0]); 109 | uv_queue_work(uv_default_loop(), &req->work, getAvailableFontsAsync, (uv_after_work_cb) asyncCallback); 110 | 111 | return; 112 | } else { 113 | info.GetReturnValue().Set(collectResults(getAvailableFonts())); 114 | } 115 | } 116 | 117 | void findFontsAsync(uv_work_t *work) { 118 | AsyncRequest *req = (AsyncRequest *) work->data; 119 | req->results = findFonts(req->desc); 120 | } 121 | 122 | template 123 | NAN_METHOD(findFonts) { 124 | if (info.Length() < 1 || !info[0]->IsObject() || info[0]->IsFunction()) 125 | return Nan::ThrowTypeError("Expected a font descriptor"); 126 | 127 | Local desc = info[0].As(); 128 | FontDescriptor *descriptor = new FontDescriptor(desc); 129 | 130 | if (async) { 131 | if (info.Length() < 2 || !info[1]->IsFunction()) 132 | return Nan::ThrowTypeError("Expected a callback"); 133 | 134 | AsyncRequest *req = new AsyncRequest(info[1]); 135 | req->desc = descriptor; 136 | uv_queue_work(uv_default_loop(), &req->work, findFontsAsync, (uv_after_work_cb) asyncCallback); 137 | 138 | return; 139 | } else { 140 | Local res = collectResults(findFonts(descriptor)); 141 | delete descriptor; 142 | info.GetReturnValue().Set(res); 143 | } 144 | } 145 | 146 | void findFontAsync(uv_work_t *work) { 147 | AsyncRequest *req = (AsyncRequest *) work->data; 148 | req->result = findFont(req->desc); 149 | } 150 | 151 | template 152 | NAN_METHOD(findFont) { 153 | if (info.Length() < 1 || !info[0]->IsObject() || info[0]->IsFunction()) 154 | return Nan::ThrowTypeError("Expected a font descriptor"); 155 | 156 | Local desc = info[0].As(); 157 | FontDescriptor *descriptor = new FontDescriptor(desc); 158 | 159 | if (async) { 160 | if (info.Length() < 2 || !info[1]->IsFunction()) 161 | return Nan::ThrowTypeError("Expected a callback"); 162 | 163 | AsyncRequest *req = new AsyncRequest(info[1]); 164 | req->desc = descriptor; 165 | uv_queue_work(uv_default_loop(), &req->work, findFontAsync, (uv_after_work_cb) asyncCallback); 166 | 167 | return; 168 | } else { 169 | Local res = wrapResult(findFont(descriptor)); 170 | delete descriptor; 171 | info.GetReturnValue().Set(res); 172 | } 173 | } 174 | 175 | void substituteFontAsync(uv_work_t *work) { 176 | AsyncRequest *req = (AsyncRequest *) work->data; 177 | req->result = substituteFont(req->postscriptName, req->substitutionString); 178 | } 179 | 180 | template 181 | NAN_METHOD(substituteFont) { 182 | if (info.Length() < 1 || !info[0]->IsString()) 183 | return Nan::ThrowTypeError("Expected postscript name"); 184 | 185 | if (info.Length() < 2 || !info[1]->IsString()) 186 | return Nan::ThrowTypeError("Expected substitution string"); 187 | 188 | Nan::Utf8String postscriptName(info[0]); 189 | Nan::Utf8String substitutionString(info[1]); 190 | 191 | if (async) { 192 | if (info.Length() < 3 || !info[2]->IsFunction()) 193 | return Nan::ThrowTypeError("Expected a callback"); 194 | 195 | // copy the strings since the JS garbage collector might run before the async request is finished 196 | char *ps = new char[postscriptName.length() + 1]; 197 | strcpy(ps, *postscriptName); 198 | 199 | char *sub = new char[substitutionString.length() + 1]; 200 | strcpy(sub, *substitutionString); 201 | 202 | AsyncRequest *req = new AsyncRequest(info[2]); 203 | req->postscriptName = ps; 204 | req->substitutionString = sub; 205 | uv_queue_work(uv_default_loop(), &req->work, substituteFontAsync, (uv_after_work_cb) asyncCallback); 206 | 207 | return; 208 | } else { 209 | info.GetReturnValue().Set(wrapResult(substituteFont(*postscriptName, *substitutionString))); 210 | } 211 | } 212 | 213 | NAN_MODULE_INIT(Init) { 214 | Nan::Export(target, "getAvailableFonts", getAvailableFonts); 215 | Nan::Export(target, "getAvailableFontsSync", getAvailableFonts); 216 | Nan::Export(target, "findFonts", findFonts); 217 | Nan::Export(target, "findFontsSync", findFonts); 218 | Nan::Export(target, "findFont", findFont); 219 | Nan::Export(target, "findFontSync", findFont); 220 | Nan::Export(target, "substituteFont", substituteFont); 221 | Nan::Export(target, "substituteFontSync", substituteFont); 222 | } 223 | 224 | NODE_MODULE(fontmanager, Init) 225 | -------------------------------------------------------------------------------- /src/FontManagerLinux.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include "FontDescriptor.h" 3 | 4 | int convertWeight(FontWeight weight) { 5 | switch (weight) { 6 | case FontWeightThin: 7 | return FC_WEIGHT_THIN; 8 | case FontWeightUltraLight: 9 | return FC_WEIGHT_ULTRALIGHT; 10 | case FontWeightLight: 11 | return FC_WEIGHT_LIGHT; 12 | case FontWeightNormal: 13 | return FC_WEIGHT_REGULAR; 14 | case FontWeightMedium: 15 | return FC_WEIGHT_MEDIUM; 16 | case FontWeightSemiBold: 17 | return FC_WEIGHT_SEMIBOLD; 18 | case FontWeightBold: 19 | return FC_WEIGHT_BOLD; 20 | case FontWeightUltraBold: 21 | return FC_WEIGHT_EXTRABOLD; 22 | case FontWeightHeavy: 23 | return FC_WEIGHT_ULTRABLACK; 24 | default: 25 | return FC_WEIGHT_REGULAR; 26 | } 27 | } 28 | 29 | FontWeight convertWeight(int weight) { 30 | switch (weight) { 31 | case FC_WEIGHT_THIN: 32 | return FontWeightThin; 33 | case FC_WEIGHT_ULTRALIGHT: 34 | return FontWeightUltraLight; 35 | case FC_WEIGHT_LIGHT: 36 | return FontWeightLight; 37 | case FC_WEIGHT_REGULAR: 38 | return FontWeightNormal; 39 | case FC_WEIGHT_MEDIUM: 40 | return FontWeightMedium; 41 | case FC_WEIGHT_SEMIBOLD: 42 | return FontWeightSemiBold; 43 | case FC_WEIGHT_BOLD: 44 | return FontWeightBold; 45 | case FC_WEIGHT_EXTRABOLD: 46 | return FontWeightUltraBold; 47 | case FC_WEIGHT_ULTRABLACK: 48 | return FontWeightHeavy; 49 | default: 50 | return FontWeightNormal; 51 | } 52 | } 53 | 54 | int convertWidth(FontWidth width) { 55 | switch (width) { 56 | case FontWidthUltraCondensed: 57 | return FC_WIDTH_ULTRACONDENSED; 58 | case FontWidthExtraCondensed: 59 | return FC_WIDTH_EXTRACONDENSED; 60 | case FontWidthCondensed: 61 | return FC_WIDTH_CONDENSED; 62 | case FontWidthSemiCondensed: 63 | return FC_WIDTH_SEMICONDENSED; 64 | case FontWidthNormal: 65 | return FC_WIDTH_NORMAL; 66 | case FontWidthSemiExpanded: 67 | return FC_WIDTH_SEMIEXPANDED; 68 | case FontWidthExpanded: 69 | return FC_WIDTH_EXPANDED; 70 | case FontWidthExtraExpanded: 71 | return FC_WIDTH_EXTRAEXPANDED; 72 | case FontWidthUltraExpanded: 73 | return FC_WIDTH_ULTRAEXPANDED; 74 | default: 75 | return FC_WIDTH_NORMAL; 76 | } 77 | } 78 | 79 | FontWidth convertWidth(int width) { 80 | switch (width) { 81 | case FC_WIDTH_ULTRACONDENSED: 82 | return FontWidthUltraCondensed; 83 | case FC_WIDTH_EXTRACONDENSED: 84 | return FontWidthExtraCondensed; 85 | case FC_WIDTH_CONDENSED: 86 | return FontWidthCondensed; 87 | case FC_WIDTH_SEMICONDENSED: 88 | return FontWidthSemiCondensed; 89 | case FC_WIDTH_NORMAL: 90 | return FontWidthNormal; 91 | case FC_WIDTH_SEMIEXPANDED: 92 | return FontWidthSemiExpanded; 93 | case FC_WIDTH_EXPANDED: 94 | return FontWidthExpanded; 95 | case FC_WIDTH_EXTRAEXPANDED: 96 | return FontWidthExtraExpanded; 97 | case FC_WIDTH_ULTRAEXPANDED: 98 | return FontWidthUltraExpanded; 99 | default: 100 | return FontWidthNormal; 101 | } 102 | } 103 | 104 | FontDescriptor *createFontDescriptor(FcPattern *pattern) { 105 | FcChar8 *path, *psName, *family, *style; 106 | int weight, width, slant, spacing; 107 | 108 | FcPatternGetString(pattern, FC_FILE, 0, &path); 109 | FcPatternGetString(pattern, FC_POSTSCRIPT_NAME, 0, &psName); 110 | FcPatternGetString(pattern, FC_FAMILY, 0, &family); 111 | FcPatternGetString(pattern, FC_STYLE, 0, &style); 112 | 113 | FcPatternGetInteger(pattern, FC_WEIGHT, 0, &weight); 114 | FcPatternGetInteger(pattern, FC_WIDTH, 0, &width); 115 | FcPatternGetInteger(pattern, FC_SLANT, 0, &slant); 116 | FcPatternGetInteger(pattern, FC_SPACING, 0, &spacing); 117 | 118 | return new FontDescriptor( 119 | (char *) path, 120 | (char *) psName, 121 | (char *) family, 122 | (char *) style, 123 | convertWeight(weight), 124 | convertWidth(width), 125 | slant == FC_SLANT_ITALIC, 126 | spacing == FC_MONO 127 | ); 128 | } 129 | 130 | ResultSet *getResultSet(FcFontSet *fs) { 131 | ResultSet *res = new ResultSet(); 132 | if (!fs) 133 | return res; 134 | 135 | for (int i = 0; i < fs->nfont; i++) { 136 | res->push_back(createFontDescriptor(fs->fonts[i])); 137 | } 138 | 139 | return res; 140 | } 141 | 142 | ResultSet *getAvailableFonts() { 143 | FcInit(); 144 | 145 | FcPattern *pattern = FcPatternCreate(); 146 | FcObjectSet *os = FcObjectSetBuild(FC_FILE, FC_POSTSCRIPT_NAME, FC_FAMILY, FC_STYLE, FC_WEIGHT, FC_WIDTH, FC_SLANT, FC_SPACING, NULL); 147 | FcFontSet *fs = FcFontList(NULL, pattern, os); 148 | ResultSet *res = getResultSet(fs); 149 | 150 | FcPatternDestroy(pattern); 151 | FcObjectSetDestroy(os); 152 | FcFontSetDestroy(fs); 153 | 154 | return res; 155 | } 156 | 157 | 158 | FcPattern *createPattern(FontDescriptor *desc) { 159 | FcInit(); 160 | FcPattern *pattern = FcPatternCreate(); 161 | 162 | if (desc->postscriptName) 163 | FcPatternAddString(pattern, FC_POSTSCRIPT_NAME, (FcChar8 *) desc->postscriptName); 164 | 165 | if (desc->family) 166 | FcPatternAddString(pattern, FC_FAMILY, (FcChar8 *) desc->family); 167 | 168 | if (desc->style) 169 | FcPatternAddString(pattern, FC_STYLE, (FcChar8 *) desc->style); 170 | 171 | if (desc->italic) 172 | FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC); 173 | 174 | if (desc->weight) 175 | FcPatternAddInteger(pattern, FC_WEIGHT, convertWeight(desc->weight)); 176 | 177 | if (desc->width) 178 | FcPatternAddInteger(pattern, FC_WIDTH, convertWidth(desc->width)); 179 | 180 | if (desc->monospace) 181 | FcPatternAddInteger(pattern, FC_SPACING, FC_MONO); 182 | 183 | return pattern; 184 | } 185 | 186 | ResultSet *findFonts(FontDescriptor *desc) { 187 | FcPattern *pattern = createPattern(desc); 188 | FcObjectSet *os = FcObjectSetBuild(FC_FILE, FC_POSTSCRIPT_NAME, FC_FAMILY, FC_STYLE, FC_WEIGHT, FC_WIDTH, FC_SLANT, FC_SPACING, NULL); 189 | FcFontSet *fs = FcFontList(NULL, pattern, os); 190 | ResultSet *res = getResultSet(fs); 191 | 192 | FcFontSetDestroy(fs); 193 | FcPatternDestroy(pattern); 194 | FcObjectSetDestroy(os); 195 | 196 | return res; 197 | } 198 | 199 | FontDescriptor *findFont(FontDescriptor *desc) { 200 | FcPattern *pattern = createPattern(desc); 201 | FcConfigSubstitute(NULL, pattern, FcMatchPattern); 202 | FcDefaultSubstitute(pattern); 203 | 204 | FcResult result; 205 | FcPattern *font = FcFontMatch(NULL, pattern, &result); 206 | FontDescriptor *res = createFontDescriptor(font); 207 | 208 | FcPatternDestroy(pattern); 209 | FcPatternDestroy(font); 210 | 211 | return res; 212 | } 213 | 214 | FontDescriptor *substituteFont(char *postscriptName, char *string) { 215 | FcInit(); 216 | 217 | // create a pattern with the postscript name 218 | FcPattern* pattern = FcPatternCreate(); 219 | FcPatternAddString(pattern, FC_POSTSCRIPT_NAME, (FcChar8 *) postscriptName); 220 | 221 | // create a charset with each character in the string 222 | FcCharSet* charset = FcCharSetCreate(); 223 | int len = strlen(string); 224 | 225 | for (int i = 0; i < len;) { 226 | FcChar32 c; 227 | i += FcUtf8ToUcs4((FcChar8 *)string + i, &c, len - i); 228 | FcCharSetAddChar(charset, c); 229 | } 230 | 231 | FcPatternAddCharSet(pattern, FC_CHARSET, charset); 232 | FcCharSetDestroy(charset); 233 | 234 | FcConfigSubstitute(0, pattern, FcMatchPattern); 235 | FcDefaultSubstitute(pattern); 236 | 237 | // find the best match font 238 | FcResult result; 239 | FcPattern *font = FcFontMatch(NULL, pattern, &result); 240 | FontDescriptor *res = createFontDescriptor(font); 241 | 242 | FcPatternDestroy(pattern); 243 | FcPatternDestroy(font); 244 | 245 | return res; 246 | } 247 | -------------------------------------------------------------------------------- /src/FontManagerMac.mm: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "FontDescriptor.h" 4 | 5 | // converts a CoreText weight (-1 to +1) to a standard weight (100 to 900) 6 | static int convertWeight(float weight) { 7 | if (weight <= -0.8f) 8 | return 100; 9 | else if (weight <= -0.6f) 10 | return 200; 11 | else if (weight <= -0.4f) 12 | return 300; 13 | else if (weight <= 0.0f) 14 | return 400; 15 | else if (weight <= 0.25f) 16 | return 500; 17 | else if (weight <= 0.35f) 18 | return 600; 19 | else if (weight <= 0.4f) 20 | return 700; 21 | else if (weight <= 0.6f) 22 | return 800; 23 | else 24 | return 900; 25 | } 26 | 27 | // converts a CoreText width (-1 to +1) to a standard width (1 to 9) 28 | static int convertWidth(float unit) { 29 | if (unit < 0) { 30 | return 1 + (1 + unit) * 4; 31 | } else { 32 | return 5 + unit * 4; 33 | } 34 | } 35 | 36 | FontDescriptor *createFontDescriptor(CTFontDescriptorRef descriptor) { 37 | NSURL *url = (NSURL *) CTFontDescriptorCopyAttribute(descriptor, kCTFontURLAttribute); 38 | NSString *psName = (NSString *) CTFontDescriptorCopyAttribute(descriptor, kCTFontNameAttribute); 39 | NSString *family = (NSString *) CTFontDescriptorCopyAttribute(descriptor, kCTFontFamilyNameAttribute); 40 | NSString *style = (NSString *) CTFontDescriptorCopyAttribute(descriptor, kCTFontStyleNameAttribute); 41 | 42 | NSDictionary *traits = (NSDictionary *) CTFontDescriptorCopyAttribute(descriptor, kCTFontTraitsAttribute); 43 | NSNumber *weightVal = traits[(id)kCTFontWeightTrait]; 44 | FontWeight weight = (FontWeight) convertWeight([weightVal floatValue]); 45 | 46 | NSNumber *widthVal = traits[(id)kCTFontWidthTrait]; 47 | FontWidth width = (FontWidth) convertWidth([widthVal floatValue]); 48 | 49 | NSNumber *symbolicTraitsVal = traits[(id)kCTFontSymbolicTrait]; 50 | unsigned int symbolicTraits = [symbolicTraitsVal unsignedIntValue]; 51 | 52 | FontDescriptor *res = new FontDescriptor( 53 | [[url path] UTF8String], 54 | [psName UTF8String], 55 | [family UTF8String], 56 | [style UTF8String], 57 | weight, 58 | width, 59 | (symbolicTraits & kCTFontItalicTrait) != 0, 60 | (symbolicTraits & kCTFontMonoSpaceTrait) != 0 61 | ); 62 | 63 | [url release]; 64 | [psName release]; 65 | [family release]; 66 | [style release]; 67 | [traits release]; 68 | return res; 69 | } 70 | 71 | ResultSet *getAvailableFonts() { 72 | // cache font collection for fast use in future calls 73 | static CTFontCollectionRef collection = NULL; 74 | if (collection == NULL) 75 | collection = CTFontCollectionCreateFromAvailableFonts(NULL); 76 | 77 | NSArray *matches = (NSArray *) CTFontCollectionCreateMatchingFontDescriptors(collection); 78 | ResultSet *results = new ResultSet(); 79 | 80 | for (id m in matches) { 81 | CTFontDescriptorRef match = (CTFontDescriptorRef) m; 82 | results->push_back(createFontDescriptor(match)); 83 | } 84 | 85 | [matches release]; 86 | return results; 87 | } 88 | 89 | // helper to square a value 90 | static inline int sqr(int value) { 91 | return value * value; 92 | } 93 | 94 | CTFontDescriptorRef getFontDescriptor(FontDescriptor *desc) { 95 | // build a dictionary of font attributes 96 | NSMutableDictionary *attrs = [NSMutableDictionary dictionary]; 97 | CTFontSymbolicTraits symbolicTraits = 0; 98 | 99 | if (desc->postscriptName) { 100 | NSString *postscriptName = [NSString stringWithUTF8String:desc->postscriptName]; 101 | attrs[(id)kCTFontNameAttribute] = postscriptName; 102 | } 103 | 104 | if (desc->family) { 105 | NSString *family = [NSString stringWithUTF8String:desc->family]; 106 | attrs[(id)kCTFontFamilyNameAttribute] = family; 107 | } 108 | 109 | if (desc->style) { 110 | NSString *style = [NSString stringWithUTF8String:desc->style]; 111 | attrs[(id)kCTFontStyleNameAttribute] = style; 112 | } 113 | 114 | // build symbolic traits 115 | if (desc->italic) 116 | symbolicTraits |= kCTFontItalicTrait; 117 | 118 | if (desc->weight == FontWeightBold) 119 | symbolicTraits |= kCTFontBoldTrait; 120 | 121 | if (desc->monospace) 122 | symbolicTraits |= kCTFontMonoSpaceTrait; 123 | 124 | if (desc->width == FontWidthCondensed) 125 | symbolicTraits |= kCTFontCondensedTrait; 126 | 127 | if (desc->width == FontWidthExpanded) 128 | symbolicTraits |= kCTFontExpandedTrait; 129 | 130 | if (symbolicTraits) { 131 | NSDictionary *traits = @{(id)kCTFontSymbolicTrait:[NSNumber numberWithUnsignedInt:symbolicTraits]}; 132 | attrs[(id)kCTFontTraitsAttribute] = traits; 133 | } 134 | 135 | // create a font descriptor and search for matches 136 | return CTFontDescriptorCreateWithAttributes((CFDictionaryRef) attrs); 137 | } 138 | 139 | int metricForMatch(CTFontDescriptorRef match, FontDescriptor *desc) { 140 | NSDictionary *dict = (NSDictionary *)CTFontDescriptorCopyAttribute(match, kCTFontTraitsAttribute); 141 | 142 | bool italic = ([dict[(id)kCTFontSymbolicTrait] unsignedIntValue] & kCTFontItalicTrait); 143 | 144 | // normalize everything to base-900 145 | int metric = 0; 146 | if (desc->weight) 147 | metric += sqr(convertWeight([dict[(id)kCTFontWeightTrait] floatValue]) - desc->weight); 148 | 149 | if (desc->width) 150 | metric += sqr((convertWidth([dict[(id)kCTFontWidthTrait] floatValue]) - desc->width) * 100); 151 | 152 | metric += sqr((italic != desc->italic) * 900); 153 | 154 | [dict release]; 155 | return metric; 156 | } 157 | 158 | ResultSet *findFonts(FontDescriptor *desc) { 159 | CTFontDescriptorRef descriptor = getFontDescriptor(desc); 160 | NSArray *matches = (NSArray *) CTFontDescriptorCreateMatchingFontDescriptors(descriptor, NULL); 161 | ResultSet *results = new ResultSet(); 162 | 163 | NSArray *sorted = [matches sortedArrayUsingComparator:^NSComparisonResult(id a, id b) { 164 | int ma = metricForMatch((CTFontDescriptorRef) a, desc); 165 | int mb = metricForMatch((CTFontDescriptorRef) b, desc); 166 | return ma < mb ? NSOrderedAscending : ma > mb ? NSOrderedDescending : NSOrderedSame; 167 | }]; 168 | 169 | for (id m in sorted) { 170 | CTFontDescriptorRef match = (CTFontDescriptorRef) m; 171 | int mb = metricForMatch((CTFontDescriptorRef) m, desc); 172 | 173 | if (mb < 10000) { 174 | results->push_back(createFontDescriptor(match)); 175 | } 176 | } 177 | 178 | CFRelease(descriptor); 179 | [matches release]; 180 | return results; 181 | } 182 | 183 | CTFontDescriptorRef findBest(FontDescriptor *desc, NSArray *matches) { 184 | // find the closest match for width and weight attributes 185 | CTFontDescriptorRef best = NULL; 186 | int bestMetric = INT_MAX; 187 | 188 | for (id m in matches) { 189 | int metric = metricForMatch((CTFontDescriptorRef) m, desc); 190 | 191 | if (metric < bestMetric) { 192 | bestMetric = metric; 193 | best = (CTFontDescriptorRef) m; 194 | } 195 | 196 | // break if this is an exact match 197 | if (metric == 0) 198 | break; 199 | } 200 | 201 | return best; 202 | } 203 | 204 | FontDescriptor *findFont(FontDescriptor *desc) { 205 | FontDescriptor *res = NULL; 206 | CTFontDescriptorRef descriptor = getFontDescriptor(desc); 207 | NSArray *matches = (NSArray *) CTFontDescriptorCreateMatchingFontDescriptors(descriptor, NULL); 208 | 209 | // if there was no match, try again but only try to match traits 210 | if ([matches count] == 0) { 211 | [matches release]; 212 | NSSet *set = [NSSet setWithObjects:(id)kCTFontTraitsAttribute, nil]; 213 | matches = (NSArray *) CTFontDescriptorCreateMatchingFontDescriptors(descriptor, (CFSetRef) set); 214 | } 215 | 216 | // find the closest match for width and weight attributes 217 | CTFontDescriptorRef best = findBest(desc, matches); 218 | 219 | // if we found a match, generate and return a URL for it 220 | if (best) { 221 | res = createFontDescriptor(best); 222 | } 223 | 224 | [matches release]; 225 | CFRelease(descriptor); 226 | return res; 227 | } 228 | 229 | FontDescriptor *substituteFont(char *postscriptName, char *string) { 230 | FontDescriptor *res = NULL; 231 | 232 | // create a font descriptor to find the font by its postscript name 233 | // we don't use CTFontCreateWithName because that supports font 234 | // names other than the postscript name but prints warnings. 235 | NSString *ps = [NSString stringWithUTF8String:postscriptName]; 236 | NSDictionary *attrs = @{(id)kCTFontNameAttribute: ps}; 237 | CTFontDescriptorRef descriptor = CTFontDescriptorCreateWithAttributes((CFDictionaryRef) attrs); 238 | CTFontRef font = CTFontCreateWithFontDescriptor(descriptor, 12.0, NULL); 239 | 240 | // find a substitute font that support the given characters 241 | NSString *str = [NSString stringWithUTF8String:string]; 242 | CTFontRef substituteFont = CTFontCreateForString(font, (CFStringRef) str, CFRangeMake(0, [str length])); 243 | CTFontDescriptorRef substituteDescriptor = CTFontCopyFontDescriptor(substituteFont); 244 | 245 | // finally, create and return a result object for this substitute font 246 | res = createFontDescriptor(substituteDescriptor); 247 | 248 | CFRelease(font); 249 | CFRelease(substituteFont); 250 | CFRelease(substituteDescriptor); 251 | 252 | return res; 253 | } 254 | -------------------------------------------------------------------------------- /src/FontManagerWindows.cc: -------------------------------------------------------------------------------- 1 | #define WINVER 0x0600 2 | #include "FontDescriptor.h" 3 | #include 4 | #include 5 | #include 6 | 7 | // throws a JS error when there is some exception in DirectWrite 8 | #define HR(hr) \ 9 | if (FAILED(hr)) throw "Font loading error"; 10 | 11 | WCHAR *utf8ToUtf16(const char *input) { 12 | unsigned int len = MultiByteToWideChar(CP_UTF8, 0, input, -1, NULL, 0); 13 | WCHAR *output = new WCHAR[len]; 14 | MultiByteToWideChar(CP_UTF8, 0, input, -1, output, len); 15 | return output; 16 | } 17 | 18 | char *utf16ToUtf8(const WCHAR *input) { 19 | unsigned int len = WideCharToMultiByte(CP_UTF8, 0, input, -1, NULL, 0, NULL, NULL); 20 | char *output = new char[len]; 21 | WideCharToMultiByte(CP_UTF8, 0, input, -1, output, len, NULL, NULL); 22 | return output; 23 | } 24 | 25 | // returns the index of the user's locale in the set of localized strings 26 | unsigned int getLocaleIndex(IDWriteLocalizedStrings *strings) { 27 | unsigned int index = 0; 28 | BOOL exists = false; 29 | wchar_t localeName[LOCALE_NAME_MAX_LENGTH]; 30 | 31 | // Get the default locale for this user. 32 | int success = GetUserDefaultLocaleName(localeName, LOCALE_NAME_MAX_LENGTH); 33 | 34 | // If the default locale is returned, find that locale name, otherwise use "en-us". 35 | if (success) { 36 | HR(strings->FindLocaleName(localeName, &index, &exists)); 37 | } 38 | 39 | // if the above find did not find a match, retry with US English 40 | if (!exists) { 41 | HR(strings->FindLocaleName(L"en-us", &index, &exists)); 42 | } 43 | 44 | if (!exists) 45 | index = 0; 46 | 47 | return index; 48 | } 49 | 50 | // gets a localized string for a font 51 | char *getString(IDWriteFont *font, DWRITE_INFORMATIONAL_STRING_ID string_id) { 52 | char *res = NULL; 53 | IDWriteLocalizedStrings *strings = NULL; 54 | 55 | BOOL exists = false; 56 | HR(font->GetInformationalStrings( 57 | string_id, 58 | &strings, 59 | &exists 60 | )); 61 | 62 | if (exists) { 63 | unsigned int index = getLocaleIndex(strings); 64 | unsigned int len = 0; 65 | WCHAR *str = NULL; 66 | 67 | HR(strings->GetStringLength(index, &len)); 68 | str = new WCHAR[len + 1]; 69 | 70 | HR(strings->GetString(index, str, len + 1)); 71 | 72 | // convert to utf8 73 | res = utf16ToUtf8(str); 74 | delete str; 75 | 76 | strings->Release(); 77 | } 78 | 79 | if (!res) { 80 | res = new char[1]; 81 | res[0] = '\0'; 82 | } 83 | 84 | return res; 85 | } 86 | 87 | FontDescriptor *resultFromFont(IDWriteFont *font) { 88 | FontDescriptor *res = NULL; 89 | IDWriteFontFace *face = NULL; 90 | unsigned int numFiles = 0; 91 | 92 | HR(font->CreateFontFace(&face)); 93 | 94 | // get the font files from this font face 95 | IDWriteFontFile *files = NULL; 96 | HR(face->GetFiles(&numFiles, NULL)); 97 | HR(face->GetFiles(&numFiles, &files)); 98 | 99 | // return the first one 100 | if (numFiles > 0) { 101 | IDWriteFontFileLoader *loader = NULL; 102 | IDWriteLocalFontFileLoader *fileLoader = NULL; 103 | unsigned int nameLength = 0; 104 | const void *referenceKey = NULL; 105 | unsigned int referenceKeySize = 0; 106 | WCHAR *name = NULL; 107 | 108 | HR(files[0].GetLoader(&loader)); 109 | 110 | // check if this is a local font file 111 | HRESULT hr = loader->QueryInterface(__uuidof(IDWriteLocalFontFileLoader), (void **)&fileLoader); 112 | if (SUCCEEDED(hr)) { 113 | // get the file path 114 | HR(files[0].GetReferenceKey(&referenceKey, &referenceKeySize)); 115 | HR(fileLoader->GetFilePathLengthFromKey(referenceKey, referenceKeySize, &nameLength)); 116 | 117 | name = new WCHAR[nameLength + 1]; 118 | HR(fileLoader->GetFilePathFromKey(referenceKey, referenceKeySize, name, nameLength + 1)); 119 | 120 | char *psName = utf16ToUtf8(name); 121 | char *postscriptName = getString(font, DWRITE_INFORMATIONAL_STRING_POSTSCRIPT_NAME); 122 | char *family = getString(font, DWRITE_INFORMATIONAL_STRING_WIN32_FAMILY_NAMES); 123 | char *style = getString(font, DWRITE_INFORMATIONAL_STRING_WIN32_SUBFAMILY_NAMES); 124 | 125 | bool monospace = false; 126 | // this method requires windows 7, so we need to cast to an IDWriteFontFace1 127 | 128 | IDWriteFontFace1 *face1; 129 | HRESULT hr = face->QueryInterface(__uuidof(IDWriteFontFace1), (void **)&face1); 130 | if (SUCCEEDED(hr)) { 131 | monospace = face1->IsMonospacedFont() == TRUE; 132 | } 133 | 134 | res = new FontDescriptor( 135 | psName, 136 | postscriptName, 137 | family, 138 | style, 139 | (FontWeight) font->GetWeight(), 140 | (FontWidth) font->GetStretch(), 141 | font->GetStyle() == DWRITE_FONT_STYLE_ITALIC, 142 | monospace 143 | ); 144 | 145 | delete psName; 146 | delete name; 147 | delete postscriptName; 148 | delete family; 149 | delete style; 150 | fileLoader->Release(); 151 | } 152 | 153 | loader->Release(); 154 | } 155 | 156 | face->Release(); 157 | files->Release(); 158 | 159 | return res; 160 | } 161 | 162 | ResultSet *getAvailableFonts() { 163 | ResultSet *res = new ResultSet(); 164 | int count = 0; 165 | 166 | IDWriteFactory *factory = NULL; 167 | HR(DWriteCreateFactory( 168 | DWRITE_FACTORY_TYPE_SHARED, 169 | __uuidof(IDWriteFactory), 170 | reinterpret_cast(&factory) 171 | )); 172 | 173 | // Get the system font collection. 174 | IDWriteFontCollection *collection = NULL; 175 | HR(factory->GetSystemFontCollection(&collection)); 176 | 177 | // Get the number of font families in the collection. 178 | int familyCount = collection->GetFontFamilyCount(); 179 | 180 | // track postscript names we've already added 181 | // using a set so we don't get any duplicates. 182 | std::unordered_set psNames; 183 | 184 | for (int i = 0; i < familyCount; i++) { 185 | IDWriteFontFamily *family = NULL; 186 | 187 | // Get the font family. 188 | HR(collection->GetFontFamily(i, &family)); 189 | int fontCount = family->GetFontCount(); 190 | 191 | for (int j = 0; j < fontCount; j++) { 192 | IDWriteFont *font = NULL; 193 | HR(family->GetFont(j, &font)); 194 | 195 | FontDescriptor *result = resultFromFont(font); 196 | if (psNames.count(result->postscriptName) == 0) { 197 | res->push_back(resultFromFont(font)); 198 | psNames.insert(result->postscriptName); 199 | } 200 | } 201 | 202 | family->Release(); 203 | } 204 | 205 | collection->Release(); 206 | factory->Release(); 207 | 208 | return res; 209 | } 210 | 211 | bool resultMatches(FontDescriptor *result, FontDescriptor *desc) { 212 | if (desc->postscriptName && strcmp(desc->postscriptName, result->postscriptName) != 0) 213 | return false; 214 | 215 | if (desc->family && strcmp(desc->family, result->family) != 0) 216 | return false; 217 | 218 | if (desc->style && strcmp(desc->style, result->style) != 0) 219 | return false; 220 | 221 | if (desc->weight && desc->weight != result->weight) 222 | return false; 223 | 224 | if (desc->width && desc->width != result->width) 225 | return false; 226 | 227 | if (desc->italic != result->italic) 228 | return false; 229 | 230 | if (desc->monospace != result->monospace) 231 | return false; 232 | 233 | return true; 234 | } 235 | 236 | ResultSet *findFonts(FontDescriptor *desc) { 237 | ResultSet *fonts = getAvailableFonts(); 238 | 239 | for (ResultSet::iterator it = fonts->begin(); it != fonts->end();) { 240 | if (!resultMatches(*it, desc)) { 241 | delete *it; 242 | it = fonts->erase(it); 243 | } else { 244 | it++; 245 | } 246 | } 247 | 248 | return fonts; 249 | } 250 | 251 | FontDescriptor *findFont(FontDescriptor *desc) { 252 | ResultSet *fonts = findFonts(desc); 253 | 254 | // if we didn't find anything, try again with only the font traits, no string names 255 | if (fonts->size() == 0) { 256 | delete fonts; 257 | 258 | FontDescriptor *fallback = new FontDescriptor( 259 | NULL, NULL, NULL, NULL, 260 | desc->weight, desc->width, desc->italic, false 261 | ); 262 | 263 | fonts = findFonts(fallback); 264 | } 265 | 266 | // ok, nothing. shouldn't happen often. 267 | // just return the first available font 268 | if (fonts->size() == 0) { 269 | delete fonts; 270 | fonts = getAvailableFonts(); 271 | } 272 | 273 | // hopefully we found something now. 274 | // copy and return the first result 275 | if (fonts->size() > 0) { 276 | FontDescriptor *res = new FontDescriptor(fonts->front()); 277 | delete fonts; 278 | return res; 279 | } 280 | 281 | // whoa, weird. no fonts installed or something went wrong. 282 | delete fonts; 283 | return NULL; 284 | } 285 | 286 | // custom text renderer used to determine the fallback font for a given char 287 | class FontFallbackRenderer : public IDWriteTextRenderer { 288 | public: 289 | IDWriteFontCollection *systemFonts; 290 | IDWriteFont *font; 291 | unsigned long refCount; 292 | 293 | FontFallbackRenderer(IDWriteFontCollection *collection) { 294 | refCount = 0; 295 | collection->AddRef(); 296 | systemFonts = collection; 297 | font = NULL; 298 | } 299 | 300 | ~FontFallbackRenderer() { 301 | if (systemFonts) 302 | systemFonts->Release(); 303 | 304 | if (font) 305 | font->Release(); 306 | } 307 | 308 | // IDWriteTextRenderer methods 309 | IFACEMETHOD(DrawGlyphRun)( 310 | void *clientDrawingContext, 311 | FLOAT baselineOriginX, 312 | FLOAT baselineOriginY, 313 | DWRITE_MEASURING_MODE measuringMode, 314 | DWRITE_GLYPH_RUN const *glyphRun, 315 | DWRITE_GLYPH_RUN_DESCRIPTION const *glyphRunDescription, 316 | IUnknown *clientDrawingEffect) { 317 | 318 | // save the font that was actually rendered 319 | return systemFonts->GetFontFromFontFace(glyphRun->fontFace, &font); 320 | } 321 | 322 | IFACEMETHOD(DrawUnderline)( 323 | void *clientDrawingContext, 324 | FLOAT baselineOriginX, 325 | FLOAT baselineOriginY, 326 | DWRITE_UNDERLINE const *underline, 327 | IUnknown *clientDrawingEffect) { 328 | return E_NOTIMPL; 329 | } 330 | 331 | 332 | IFACEMETHOD(DrawStrikethrough)( 333 | void *clientDrawingContext, 334 | FLOAT baselineOriginX, 335 | FLOAT baselineOriginY, 336 | DWRITE_STRIKETHROUGH const *strikethrough, 337 | IUnknown *clientDrawingEffect) { 338 | return E_NOTIMPL; 339 | } 340 | 341 | 342 | IFACEMETHOD(DrawInlineObject)( 343 | void *clientDrawingContext, 344 | FLOAT originX, 345 | FLOAT originY, 346 | IDWriteInlineObject *inlineObject, 347 | BOOL isSideways, 348 | BOOL isRightToLeft, 349 | IUnknown *clientDrawingEffect) { 350 | return E_NOTIMPL; 351 | } 352 | 353 | // IDWritePixelSnapping methods 354 | IFACEMETHOD(IsPixelSnappingDisabled)(void *clientDrawingContext, BOOL *isDisabled) { 355 | *isDisabled = FALSE; 356 | return S_OK; 357 | } 358 | 359 | IFACEMETHOD(GetCurrentTransform)(void *clientDrawingContext, DWRITE_MATRIX *transform) { 360 | const DWRITE_MATRIX ident = {1.0, 0.0, 0.0, 1.0, 0.0, 0.0}; 361 | *transform = ident; 362 | return S_OK; 363 | } 364 | 365 | IFACEMETHOD(GetPixelsPerDip)(void *clientDrawingContext, FLOAT *pixelsPerDip) { 366 | *pixelsPerDip = 1.0f; 367 | return S_OK; 368 | } 369 | 370 | // IUnknown methods 371 | IFACEMETHOD_(unsigned long, AddRef)() { 372 | return InterlockedIncrement(&refCount); 373 | } 374 | 375 | IFACEMETHOD_(unsigned long, Release)() { 376 | unsigned long newCount = InterlockedDecrement(&refCount); 377 | if (newCount == 0) { 378 | delete this; 379 | return 0; 380 | } 381 | 382 | return newCount; 383 | } 384 | 385 | IFACEMETHOD(QueryInterface)(IID const& riid, void **ppvObject) { 386 | if (__uuidof(IDWriteTextRenderer) == riid) { 387 | *ppvObject = this; 388 | } else if (__uuidof(IDWritePixelSnapping) == riid) { 389 | *ppvObject = this; 390 | } else if (__uuidof(IUnknown) == riid) { 391 | *ppvObject = this; 392 | } else { 393 | *ppvObject = nullptr; 394 | return E_FAIL; 395 | } 396 | 397 | this->AddRef(); 398 | return S_OK; 399 | } 400 | }; 401 | 402 | FontDescriptor *substituteFont(char *postscriptName, char *string) { 403 | FontDescriptor *res = NULL; 404 | 405 | IDWriteFactory *factory = NULL; 406 | HR(DWriteCreateFactory( 407 | DWRITE_FACTORY_TYPE_SHARED, 408 | __uuidof(IDWriteFactory), 409 | reinterpret_cast(&factory) 410 | )); 411 | 412 | // Get the system font collection. 413 | IDWriteFontCollection *collection = NULL; 414 | HR(factory->GetSystemFontCollection(&collection)); 415 | 416 | // find the font for the given postscript name 417 | FontDescriptor *desc = new FontDescriptor(); 418 | desc->postscriptName = postscriptName; 419 | FontDescriptor *font = findFont(desc); 420 | 421 | // create a text format object for this font 422 | IDWriteTextFormat *format = NULL; 423 | if (font) { 424 | WCHAR *familyName = utf8ToUtf16(font->family); 425 | 426 | // create a text format 427 | HR(factory->CreateTextFormat( 428 | familyName, 429 | collection, 430 | (DWRITE_FONT_WEIGHT) font->weight, 431 | font->italic ? DWRITE_FONT_STYLE_ITALIC : DWRITE_FONT_STYLE_NORMAL, 432 | (DWRITE_FONT_STRETCH) font->width, 433 | 12.0, 434 | L"en-us", 435 | &format 436 | )); 437 | 438 | delete familyName; 439 | delete font; 440 | } else { 441 | // this should never happen, but just in case, let the system 442 | // decide the default font in case findFont returned nothing. 443 | HR(factory->CreateTextFormat( 444 | L"", 445 | collection, 446 | DWRITE_FONT_WEIGHT_REGULAR, 447 | DWRITE_FONT_STYLE_NORMAL, 448 | DWRITE_FONT_STRETCH_NORMAL, 449 | 12.0, 450 | L"en-us", 451 | &format 452 | )); 453 | } 454 | 455 | // convert utf8 string for substitution to utf16 456 | WCHAR *str = utf8ToUtf16(string); 457 | 458 | // create a text layout for the substitution string 459 | IDWriteTextLayout *layout = NULL; 460 | HR(factory->CreateTextLayout( 461 | str, 462 | wcslen(str), 463 | format, 464 | 100.0, 465 | 100.0, 466 | &layout 467 | )); 468 | 469 | // render it using a custom renderer that saves the physical font being used 470 | FontFallbackRenderer *renderer = new FontFallbackRenderer(collection); 471 | HR(layout->Draw(NULL, renderer, 100.0, 100.0)); 472 | 473 | // if we found something, create a result object 474 | if (renderer->font) { 475 | res = resultFromFont(renderer->font); 476 | } 477 | 478 | // free all the things 479 | delete renderer; 480 | layout->Release(); 481 | format->Release(); 482 | 483 | desc->postscriptName = NULL; 484 | delete desc; 485 | delete str; 486 | collection->Release(); 487 | factory->Release(); 488 | 489 | return res; 490 | } 491 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var fontManager = require('../'); 2 | var assert = require('assert'); 3 | 4 | // some standard fonts that are likely to be installed on the platform the tests are running on 5 | var standardFont = process.platform === 'linux' ? 'Liberation Sans' : 'Arial'; 6 | var postscriptName = process.platform === 'linux' ? 'LiberationSans' : 'ArialMT'; 7 | 8 | describe('font-manager', function() { 9 | it('should export some functions', function() { 10 | assert.equal(typeof fontManager.getAvailableFonts, 'function'); 11 | assert.equal(typeof fontManager.getAvailableFontsSync, 'function'); 12 | assert.equal(typeof fontManager.findFonts, 'function'); 13 | assert.equal(typeof fontManager.findFontsSync, 'function'); 14 | assert.equal(typeof fontManager.findFont, 'function'); 15 | assert.equal(typeof fontManager.findFontSync, 'function'); 16 | assert.equal(typeof fontManager.substituteFont, 'function'); 17 | assert.equal(typeof fontManager.substituteFontSync, 'function'); 18 | }); 19 | 20 | function assertFontDescriptor(font) { 21 | assert.equal(typeof font, 'object'); 22 | assert.equal(typeof font.path, 'string'); 23 | assert.equal(typeof font.postscriptName, 'string'); 24 | assert.equal(typeof font.family, 'string'); 25 | assert.equal(typeof font.style, 'string'); 26 | assert.equal(typeof font.weight, 'number'); 27 | assert.equal(typeof font.width, 'number'); 28 | assert.equal(typeof font.italic, 'boolean'); 29 | assert.equal(typeof font.monospace, 'boolean'); 30 | } 31 | 32 | describe('getAvailableFonts', function() { 33 | it('should throw if no callback is provided', function() { 34 | assert.throws(function() { 35 | fontManager.getAvailableFonts(); 36 | }, /Expected a callback/); 37 | }); 38 | 39 | it('should throw if callback is not a function', function() { 40 | assert.throws(function() { 41 | fontManager.getAvailableFonts(2); 42 | }, /Expected a callback/); 43 | }); 44 | 45 | it('should getAvailableFonts asynchronously', function(done) { 46 | var async = false; 47 | 48 | fontManager.getAvailableFonts(function(fonts) { 49 | assert(async); 50 | assert(Array.isArray(fonts)); 51 | assert(fonts.length > 0); 52 | fonts.forEach(assertFontDescriptor); 53 | done(); 54 | }); 55 | 56 | async = true; 57 | }); 58 | }); 59 | 60 | describe('getAvailableFontsSync', function() { 61 | it('should getAvailableFonts synchronously', function() { 62 | var fonts = fontManager.getAvailableFontsSync(); 63 | assert(Array.isArray(fonts)); 64 | assert(fonts.length > 0); 65 | fonts.forEach(assertFontDescriptor); 66 | }); 67 | }); 68 | 69 | describe('findFonts', function() { 70 | it('should throw if no font descriptor is provided', function() { 71 | assert.throws(function() { 72 | fontManager.findFonts(function(fonts) {}); 73 | }, /Expected a font descriptor/); 74 | }); 75 | 76 | it('should throw if font descriptor is not an object', function() { 77 | assert.throws(function() { 78 | fontManager.findFonts(2, function(fonts) {}); 79 | }, /Expected a font descriptor/); 80 | }); 81 | 82 | it('should throw if no callback is provided', function() { 83 | assert.throws(function() { 84 | fontManager.findFonts({ family: standardFont }); 85 | }, /Expected a callback/); 86 | }); 87 | 88 | it('should throw if callback is not a function', function() { 89 | assert.throws(function() { 90 | fontManager.findFonts({ family: standardFont }, 2); 91 | }, /Expected a callback/); 92 | }); 93 | 94 | it('should findFonts asynchronously', function(done) { 95 | var async = false; 96 | 97 | fontManager.findFonts({ family: standardFont }, function(fonts) { 98 | assert(async); 99 | assert(Array.isArray(fonts)); 100 | assert(fonts.length > 0); 101 | fonts.forEach(assertFontDescriptor); 102 | done(); 103 | }); 104 | 105 | async = true; 106 | }); 107 | 108 | it('should find fonts by postscriptName', function(done) { 109 | fontManager.findFonts({ postscriptName: postscriptName }, function(fonts) { 110 | assert(Array.isArray(fonts)); 111 | assert.equal(fonts.length, 1); 112 | fonts.forEach(assertFontDescriptor); 113 | assert.equal(fonts[0].postscriptName, postscriptName); 114 | assert.equal(fonts[0].family, standardFont); 115 | done(); 116 | }); 117 | }); 118 | 119 | it('should find fonts by family and style', function(done) { 120 | fontManager.findFonts({ family: standardFont, style: 'Bold' }, function(fonts) { 121 | assert(Array.isArray(fonts)); 122 | assert.equal(fonts.length, 1); 123 | fonts.forEach(assertFontDescriptor); 124 | assert.equal(fonts[0].family, standardFont); 125 | assert.equal(fonts[0].style, 'Bold'); 126 | assert.equal(fonts[0].weight, 700); 127 | done(); 128 | }); 129 | }); 130 | 131 | it('should find fonts by weight', function(done) { 132 | fontManager.findFonts({ family: standardFont, weight: 700 }, function(fonts) { 133 | assert(Array.isArray(fonts)); 134 | assert(fonts.length > 0); 135 | fonts.forEach(assertFontDescriptor); 136 | fonts.forEach(function(font) { 137 | assert.equal(font.weight, 700); 138 | }); 139 | done(); 140 | }); 141 | }); 142 | 143 | it('should find italic fonts', function(done) { 144 | fontManager.findFonts({ family: standardFont, italic: true }, function(fonts) { 145 | assert(Array.isArray(fonts)); 146 | assert(fonts.length > 0); 147 | fonts.forEach(assertFontDescriptor); 148 | fonts.forEach(function(font) { 149 | assert.equal(font.italic, true); 150 | }); 151 | done(); 152 | }); 153 | }); 154 | 155 | it('should find italic and bold fonts', function(done) { 156 | fontManager.findFonts({ family: standardFont, italic: true, weight: 700 }, function(fonts) { 157 | assert(Array.isArray(fonts)); 158 | assert(fonts.length > 0); 159 | fonts.forEach(assertFontDescriptor); 160 | fonts.forEach(function(font) { 161 | assert.equal(font.italic, true); 162 | assert.equal(font.weight, 700); 163 | }); 164 | done(); 165 | }); 166 | }); 167 | 168 | it('should return an empty array for nonexistent family', function(done) { 169 | fontManager.findFonts({ family: '' + Date.now() }, function(fonts) { 170 | assert(Array.isArray(fonts)); 171 | assert.equal(fonts.length, 0); 172 | done(); 173 | }); 174 | }); 175 | 176 | it('should return an empty array for nonexistent postscriptName', function(done) { 177 | fontManager.findFonts({ postscriptName: '' + Date.now() }, function(fonts) { 178 | assert(Array.isArray(fonts)); 179 | assert.equal(fonts.length, 0); 180 | done(); 181 | }); 182 | }); 183 | 184 | it('should return many fonts for empty font descriptor', function(done) { 185 | fontManager.findFonts({}, function(fonts) { 186 | assert(Array.isArray(fonts)); 187 | assert(fonts.length > 0); 188 | fonts.forEach(assertFontDescriptor); 189 | done(); 190 | }); 191 | }); 192 | }); 193 | 194 | describe('findFontsSync', function() { 195 | it('should throw if no font descriptor is provided', function() { 196 | assert.throws(function() { 197 | fontManager.findFontsSync(); 198 | }, /Expected a font descriptor/); 199 | }); 200 | 201 | it('should throw if font descriptor is not an object', function() { 202 | assert.throws(function() { 203 | fontManager.findFontsSync(2); 204 | }, /Expected a font descriptor/); 205 | }); 206 | 207 | it('should findFonts synchronously', function() { 208 | var fonts = fontManager.findFontsSync({ family: standardFont }); 209 | assert(Array.isArray(fonts)); 210 | assert(fonts.length > 0); 211 | fonts.forEach(assertFontDescriptor); 212 | }); 213 | 214 | it('should find fonts by postscriptName', function() { 215 | var fonts = fontManager.findFontsSync({ postscriptName: postscriptName }); 216 | assert(Array.isArray(fonts)); 217 | assert.equal(fonts.length, 1); 218 | fonts.forEach(assertFontDescriptor); 219 | assert.equal(fonts[0].postscriptName, postscriptName); 220 | assert.equal(fonts[0].family, standardFont); 221 | }); 222 | 223 | it('should find fonts by family and style', function() { 224 | var fonts = fontManager.findFontsSync({ family: standardFont, style: 'Bold' }); 225 | assert(Array.isArray(fonts)); 226 | assert.equal(fonts.length, 1); 227 | fonts.forEach(assertFontDescriptor); 228 | assert.equal(fonts[0].family, standardFont); 229 | assert.equal(fonts[0].style, 'Bold'); 230 | assert.equal(fonts[0].weight, 700); 231 | }); 232 | 233 | it('should find fonts by weight', function() { 234 | var fonts = fontManager.findFontsSync({ family: standardFont, weight: 700 }); 235 | assert(Array.isArray(fonts)); 236 | assert(fonts.length > 0); 237 | fonts.forEach(assertFontDescriptor); 238 | assert.equal(fonts[0].weight, 700); 239 | }); 240 | 241 | it('should find italic fonts', function() { 242 | var fonts = fontManager.findFontsSync({ family: standardFont, italic: true }); 243 | assert(Array.isArray(fonts)); 244 | assert(fonts.length > 0); 245 | fonts.forEach(assertFontDescriptor); 246 | assert.equal(fonts[0].italic, true); 247 | }); 248 | 249 | it('should find italic and bold fonts', function() { 250 | var fonts = fontManager.findFontsSync({ family: standardFont, italic: true, weight: 700 }); 251 | assert(Array.isArray(fonts)); 252 | assert(fonts.length > 0); 253 | fonts.forEach(assertFontDescriptor); 254 | assert.equal(fonts[0].italic, true); 255 | assert.equal(fonts[0].weight, 700); 256 | }); 257 | 258 | it('should return an empty array for nonexistent family', function() { 259 | var fonts = fontManager.findFontsSync({ family: '' + Date.now() }); 260 | assert(Array.isArray(fonts)); 261 | assert.equal(fonts.length, 0); 262 | }); 263 | 264 | it('should return an empty array for nonexistent postscriptName', function() { 265 | var fonts = fontManager.findFontsSync({ postscriptName: '' + Date.now() }); 266 | assert(Array.isArray(fonts)); 267 | assert.equal(fonts.length, 0); 268 | }); 269 | 270 | it('should return many fonts for empty font descriptor', function() { 271 | var fonts = fontManager.findFontsSync({}); 272 | assert(Array.isArray(fonts)); 273 | assert(fonts.length > 0); 274 | fonts.forEach(assertFontDescriptor); 275 | }); 276 | }); 277 | 278 | describe('findFont', function() { 279 | it('should throw if no font descriptor is provided', function() { 280 | assert.throws(function() { 281 | fontManager.findFont(function(fonts) {}); 282 | }, /Expected a font descriptor/); 283 | }); 284 | 285 | it('should throw if font descriptor is not an object', function() { 286 | assert.throws(function() { 287 | fontManager.findFont(2, function(fonts) {}); 288 | }, /Expected a font descriptor/); 289 | }); 290 | 291 | it('should throw if no callback is provided', function() { 292 | assert.throws(function() { 293 | fontManager.findFont({ family: standardFont }); 294 | }, /Expected a callback/); 295 | }); 296 | 297 | it('should throw if callback is not a function', function() { 298 | assert.throws(function() { 299 | fontManager.findFont({ family: standardFont }, 2); 300 | }, /Expected a callback/); 301 | }); 302 | 303 | it('should findFont asynchronously', function(done) { 304 | var async = false; 305 | 306 | fontManager.findFont({ family: standardFont }, function(font) { 307 | assert(async); 308 | assert.equal(typeof font, 'object'); 309 | assert(!Array.isArray(font)); 310 | assertFontDescriptor(font); 311 | assert.equal(font.family, standardFont); 312 | done(); 313 | }); 314 | 315 | async = true; 316 | }); 317 | 318 | it('should find font by postscriptName', function(done) { 319 | fontManager.findFont({ postscriptName: postscriptName }, function(font) { 320 | assertFontDescriptor(font); 321 | assert.equal(font.postscriptName, postscriptName); 322 | assert.equal(font.family, standardFont); 323 | done(); 324 | }); 325 | }); 326 | 327 | it('should find font by family and style', function(done) { 328 | fontManager.findFont({ family: standardFont, style: 'Bold' }, function(font) { 329 | assertFontDescriptor(font); 330 | assert.equal(font.family, standardFont); 331 | assert.equal(font.style, 'Bold'); 332 | assert.equal(font.weight, 700); 333 | done(); 334 | }); 335 | }); 336 | 337 | it('should find font by weight', function(done) { 338 | fontManager.findFont({ family: standardFont, weight: 700 }, function(font) { 339 | assertFontDescriptor(font); 340 | assert.equal(font.weight, 700); 341 | done(); 342 | }); 343 | }); 344 | 345 | it('should find italic font', function(done) { 346 | fontManager.findFont({ family: standardFont, italic: true }, function(font) { 347 | assertFontDescriptor(font); 348 | assert.equal(font.italic, true); 349 | done(); 350 | }); 351 | }); 352 | 353 | it('should find bold italic font', function(done) { 354 | fontManager.findFont({ family: standardFont, italic: true, weight: 700 }, function(font) { 355 | assertFontDescriptor(font); 356 | assert.equal(font.italic, true); 357 | assert.equal(font.weight, 700); 358 | done(); 359 | }); 360 | }); 361 | 362 | it('should return a fallback font for nonexistent family', function(done) { 363 | fontManager.findFont({ family: '' + Date.now() }, function(font) { 364 | assertFontDescriptor(font); 365 | done(); 366 | }); 367 | }); 368 | 369 | it('should return a fallback font for nonexistent postscriptName', function(done) { 370 | fontManager.findFont({ postscriptName: '' + Date.now() }, function(font) { 371 | assertFontDescriptor(font); 372 | done(); 373 | }); 374 | }); 375 | 376 | it('should return a fallback font matching traits as best as possible', function(done) { 377 | fontManager.findFont({ family: '' + Date.now(), weight: 700 }, function(font) { 378 | assertFontDescriptor(font); 379 | assert.equal(font.weight, 700); 380 | done(); 381 | }); 382 | }); 383 | 384 | it('should return a font for empty font descriptor', function(done) { 385 | fontManager.findFont({}, function(font) { 386 | assertFontDescriptor(font); 387 | done(); 388 | }); 389 | }); 390 | }); 391 | 392 | describe('findFontSync', function() { 393 | it('should throw if no font descriptor is provided', function() { 394 | assert.throws(function() { 395 | fontManager.findFontSync(); 396 | }, /Expected a font descriptor/); 397 | }); 398 | 399 | it('should throw if font descriptor is not an object', function() { 400 | assert.throws(function() { 401 | fontManager.findFontSync(2); 402 | }, /Expected a font descriptor/); 403 | }); 404 | 405 | it('should findFonts synchronously', function() { 406 | var font = fontManager.findFontSync({ family: standardFont }); 407 | assert.equal(typeof font, 'object'); 408 | assert(!Array.isArray(font)); 409 | assertFontDescriptor(font); 410 | }); 411 | 412 | it('should find font by postscriptName', function() { 413 | var font = fontManager.findFontSync({ postscriptName: postscriptName }); 414 | assertFontDescriptor(font); 415 | assert.equal(font.postscriptName, postscriptName); 416 | assert.equal(font.family, standardFont); 417 | }); 418 | 419 | it('should find font by family and style', function() { 420 | var font = fontManager.findFontSync({ family: standardFont, style: 'Bold' }); 421 | assertFontDescriptor(font); 422 | assert.equal(font.family, standardFont); 423 | assert.equal(font.style, 'Bold'); 424 | assert.equal(font.weight, 700); 425 | }); 426 | 427 | it('should find font by weight', function() { 428 | var font = fontManager.findFontSync({ family: standardFont, weight: 700 }); 429 | assertFontDescriptor(font); 430 | assert.equal(font.weight, 700); 431 | }); 432 | 433 | it('should find italic font', function() { 434 | var font = fontManager.findFontSync({ family: standardFont, italic: true }); 435 | assertFontDescriptor(font); 436 | assert.equal(font.italic, true); 437 | }); 438 | 439 | it('should find bold italic font', function() { 440 | var font = fontManager.findFontSync({ family: standardFont, italic: true, weight: 700 }); 441 | assertFontDescriptor(font); 442 | assert.equal(font.italic, true); 443 | assert.equal(font.weight, 700); 444 | }); 445 | 446 | it('should return a fallback font for nonexistent family', function() { 447 | var font = fontManager.findFontSync({ family: '' + Date.now() }); 448 | assertFontDescriptor(font); 449 | }); 450 | 451 | it('should return a fallback font for nonexistent postscriptName', function() { 452 | var font = fontManager.findFontSync({ postscriptName: '' + Date.now() }); 453 | assertFontDescriptor(font); 454 | }); 455 | 456 | it('should return a fallback font matching traits as best as possible', function() { 457 | var font = fontManager.findFontSync({ family: '' + Date.now(), weight: 700 }); 458 | assertFontDescriptor(font); 459 | assert.equal(font.weight, 700); 460 | }); 461 | 462 | it('should return a font for empty font descriptor', function() { 463 | var font = fontManager.findFontSync({}); 464 | assertFontDescriptor(font); 465 | }); 466 | }); 467 | 468 | describe('substituteFont', function() { 469 | it('should throw if no postscript name is provided', function() { 470 | assert.throws(function() { 471 | fontManager.substituteFont(function(font) {}); 472 | }, /Expected postscript name/); 473 | }); 474 | 475 | it('should throw if postscript name is not a string', function() { 476 | assert.throws(function() { 477 | fontManager.substituteFont(2, 'hi', function(font) {}); 478 | }, /Expected postscript name/); 479 | }); 480 | 481 | it('should throw if no substitution string is provided', function() { 482 | assert.throws(function() { 483 | fontManager.substituteFont(postscriptName, function(font) {}); 484 | }, /Expected substitution string/); 485 | }); 486 | 487 | it('should throw if substitution string is not a string', function() { 488 | assert.throws(function() { 489 | fontManager.substituteFont(postscriptName, 2, function(font) {}); 490 | }, /Expected substitution string/); 491 | }); 492 | 493 | it('should throw if no callback is provided', function() { 494 | assert.throws(function() { 495 | fontManager.substituteFont(postscriptName, '汉字'); 496 | }, /Expected a callback/); 497 | }); 498 | 499 | it('should throw if callback is not a function', function() { 500 | assert.throws(function() { 501 | fontManager.substituteFont(postscriptName, '汉字', 52); 502 | }, /Expected a callback/); 503 | }); 504 | 505 | it('should substituteFont asynchronously', function(done) { 506 | var async = false; 507 | 508 | fontManager.substituteFont(postscriptName, '汉字', function(font) { 509 | assert(async); 510 | assert.equal(typeof font, 'object'); 511 | assert(!Array.isArray(font)); 512 | assertFontDescriptor(font); 513 | assert.notEqual(font.postscriptName, postscriptName); 514 | done(); 515 | }); 516 | 517 | async = true; 518 | }); 519 | 520 | it('should return the same font if it already contains the requested characters', function(done) { 521 | fontManager.substituteFont(postscriptName, 'hi', function(font) { 522 | assertFontDescriptor(font); 523 | assert.equal(font.postscriptName, postscriptName); 524 | done(); 525 | }); 526 | }); 527 | 528 | it('should return a default font if no font exists for the given postscriptName', function(done) { 529 | fontManager.substituteFont('' + Date.now(), '汉字', function(font) { 530 | assertFontDescriptor(font); 531 | done(); 532 | }); 533 | }); 534 | }); 535 | 536 | describe('substituteFontSync', function() { 537 | it('should throw if no postscript name is provided', function() { 538 | assert.throws(function() { 539 | fontManager.substituteFontSync(); 540 | }, /Expected postscript name/); 541 | }); 542 | 543 | it('should throw if postscript name is not a string', function() { 544 | assert.throws(function() { 545 | fontManager.substituteFontSync(2, 'hi'); 546 | }, /Expected postscript name/); 547 | }); 548 | 549 | it('should throw if no substitution string is provided', function() { 550 | assert.throws(function() { 551 | fontManager.substituteFontSync(postscriptName); 552 | }, /Expected substitution string/); 553 | }); 554 | 555 | it('should throw if substitution string is not a string', function() { 556 | assert.throws(function() { 557 | fontManager.substituteFontSync(postscriptName, 2); 558 | }, /Expected substitution string/); 559 | }); 560 | 561 | it('should substituteFont synchronously', function() { 562 | var font = fontManager.substituteFontSync(postscriptName, '汉字'); 563 | assert.equal(typeof font, 'object'); 564 | assert(!Array.isArray(font)); 565 | assertFontDescriptor(font); 566 | assert.notEqual(font.postscriptName, postscriptName); 567 | }); 568 | 569 | it('should return the same font if it already contains the requested characters', function() { 570 | var font = fontManager.substituteFontSync(postscriptName, 'hi'); 571 | assertFontDescriptor(font); 572 | assert.equal(font.postscriptName, postscriptName); 573 | }); 574 | 575 | it('should return a default font if no font exists for the given postscriptName', function() { 576 | var font = fontManager.substituteFontSync('' + Date.now(), '汉字'); 577 | assertFontDescriptor(font); 578 | }); 579 | }); 580 | }); 581 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --reporter spec -------------------------------------------------------------------------------- /travis-linux.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # make sure we install the new version of libfontconfig1-dev 4 | sudo apt-add-repository 'deb http://archive.ubuntu.com/ubuntu trusty main restricted universe multiverse' 5 | sudo apt-get -yqq update 6 | sudo apt-get install -y -t trusty libfontconfig1-dev 7 | 8 | # install some fonts needed for the tests 9 | sudo apt-get install -y fonts-droid fonts-liberation 10 | --------------------------------------------------------------------------------