Repository: foliojs/font-manager Branch: master Commit: 33b102e7d25f Files: 17 Total size: 73.6 KB Directory structure: gitextract_4ii78zka/ ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── appveyor.yml ├── binding.gyp ├── index.d.ts ├── package.json ├── src/ │ ├── FontDescriptor.h │ ├── FontManager.cc │ ├── FontManagerLinux.cc │ ├── FontManagerMac.mm │ └── FontManagerWindows.cc ├── test/ │ ├── index.js │ └── mocha.opts └── travis-linux.sh ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .DS_Store build/ node_modules/ ================================================ FILE: .npmignore ================================================ .DS_Store build/ node_modules/ .travis.yml appveyor.yml travis-linux.sh travis-osx.sh ================================================ FILE: .travis.yml ================================================ # setup build matrix env: - NODE_VERSION=6 - NODE_VERSION=8 - NODE_VERSION=10 addons: apt: sources: - ubuntu-toolchain-r-test packages: - g++-4.8 os: - linux - osx before_install: # install nvm if it isn't already installed (the mac VMs don't have it) - if ! command -v nvm &> /dev/null; then curl -s https://raw.githubusercontent.com/creationix/nvm/v0.13.1/install.sh | bash; fi - . ~/.nvm/nvm.sh # install node - nvm install $NODE_VERSION - npm config set spin false - node --version - npm --version install: # on linux, install some dependencies and fonts used for tests - if [ "${TRAVIS_OS_NAME}" = "linux" ]; then ./travis-linux.sh; fi # Require c++11 support - if [ "${TRAVIS_OS_NAME}" = "linux" ]; then export CXX=g++-4.8; fi # now, actually install the module - npm install script: - npm test ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2014-present 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. ================================================ FILE: README.md ================================================ [![Build Status](https://travis-ci.org/devongovett/font-manager.svg)](https://travis-ci.org/devongovett/font-manager) # font-manager A C++ module for Node.js providing access to the system font catalog. ## Features * List all available fonts * Find fonts with specified characteristics * Font substitution when characters are missing ## Platforms * Mac OS X 10.5 and later supported via [CoreText](https://developer.apple.com/library/mac/documentation/Carbon/reference/CoreText_Framework_Ref/_index.html) * Windows 7 and later supported via [DirectWrite](http://msdn.microsoft.com/en-us/library/windows/desktop/dd368038(v=vs.85).aspx) * Linux supported via [fontconfig](http://www.freedesktop.org/software/fontconfig) ## Installation Installation of the `font-manager` module is via npm: npm install font-manager On Linux, you also may need to install the `libfontconfig-dev` package, for example: sudo apt-get install libfontconfig-dev ## API You load the `font-manager` module using `require` as with all Node modules: ```javascript var fontManager = require('font-manager'); ``` All of the methods exported by `font-manager` have both synchronous and asynchronous versions available. You should generally prefer the asynchronous version as it will allow your program to continue doing other processing while a request for fonts is processing in the background, which may be expensive depending on the platform APIs that are available. * [`getAvailableFonts()`](#getavailablefonts) * [`findFonts(fontDescriptor)`](#findfontsfontdescriptor) * [`findFont(fontDescriptor)`](#findfontfontdescriptor) * [`substituteFont(postscriptName, text)`](#substitutefontpostscriptname-text) ### getAvailableFonts() Returns an array of all [font descriptors](#font-descriptor) available on the system. ```javascript // asynchronous API fontManager.getAvailableFonts(function(fonts) { ... }); // synchronous API var fonts = fontManager.getAvailableFontsSync(); // output [ { path: '/Library/Fonts/Arial.ttf', postscriptName: 'ArialMT', family: 'Arial', style: 'Regular', weight: 400, width: 5, italic: false, monospace: false }, ... ] ``` ### findFonts(fontDescriptor) Returns an array of [font descriptors](#font-descriptor) matching a query [font descriptor](#font-descriptor). The returned array may be empty if no fonts match the font descriptor. ```javascript // asynchronous API fontManager.findFonts({ family: 'Arial' }, function(fonts) { ... }); // synchronous API var fonts = fontManager.findFontsSync({ family: 'Arial' }); // output [ { path: '/Library/Fonts/Arial.ttf', postscriptName: 'ArialMT', family: 'Arial', style: 'Regular', weight: 400, width: 5, italic: false, monospace: false }, { path: '/Library/Fonts/Arial Bold.ttf', postscriptName: 'Arial-BoldMT', family: 'Arial', style: 'Bold', weight: 700, width: 5, italic: false, monospace: false } ] ``` ### findFont(fontDescriptor) Returns a single [font descriptors](#font-descriptor) matching a query [font descriptors](#font-descriptor) as well as possible. This method always returns a result (never `null`), so sometimes the output will not exactly match the input font descriptor if not all input parameters could be met. ```javascript // asynchronous API fontManager.findFont({ family: 'Arial', weight: 700 }, function(font) { ... }); // synchronous API var font = fontManager.findFontSync({ family: 'Arial', weight: 700 }); // output { path: '/Library/Fonts/Arial Bold.ttf', postscriptName: 'Arial-BoldMT', family: 'Arial', style: 'Bold', weight: 700, width: 5, italic: false, monospace: false } ``` ### substituteFont(postscriptName, text) Substitutes the font with the given `postscriptName` with another font that contains the characters in `text`. If a font matching `postscriptName` is not found, a font containing the given characters is still returned. If a font matching `postscriptName` *is* found, its characteristics (bold, italic, etc.) are used to find a suitable replacement. If the font already contains the characters in `text`, it is not replaced and the font descriptor for the original font is returned. ```javascript // asynchronous API fontManager.substituteFont('TimesNewRomanPSMT', '汉字', function(font) { ... }); // synchronous API var font = fontManager.substituteFontSync('TimesNewRomanPSMT', '汉字'); // output { path: '/Library/Fonts/Songti.ttc', postscriptName: 'STSongti-SC-Regular', family: 'Songti SC', style: 'Regular', weight: 400, width: 5, italic: false, monospace: false } ``` ### Font Descriptor Font descriptors are normal JavaScript objects that describe characteristics of a font. They are passed to the `findFonts` and `findFont` methods and returned by all of the methods. Any combination of the fields documented below can be used to find fonts, but all methods return full font descriptors. Name | Type | Description ---------------- | ------- | ----------- `path` | string | The path to the font file in the filesystem. **(not applicable for queries, only for results)** `postscriptName` | string | The PostScript name of the font (e.g `'Arial-BoldMT'`). This uniquely identities a font in most cases. `family` | string | The font family name (e.g `'Arial'`) `style` | string | The font style name (e.g. `'Bold'`) `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. `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. `italic` | boolean | Whether the font is italic or not. `monospace` | boolean | Whether the font is monospace or not. #### Weights Value | Name ----- | ------------------------- 100 | Thin 200 | Ultra Light 300 | Light 400 | Normal 500 | Medium 600 | Semi Bold 700 | Bold 800 | Ultra Bold 900 | Heavy #### Widths Value | Name ----- | ----------------------------- 1 | Ultra Condensed 2 | Extra Condensed 3 | Condensed 4 | Semi Condensed 5 | Normal 6 | Semi Expanded 7 | Expanded 8 | Extra Expanded 9 | Ultra Expanded ## License MIT ================================================ FILE: appveyor.yml ================================================ os: Windows Server 2012 environment: VisualStudioVersion: 11.0 matrix: - nodejs_version: "6" - nodejs_version: "8" - nodejs_version: "10" install: - ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version) - npm install test_script: - node --version - npm --version - ps: "npm test # PowerShell" # Pass comment to PS for easier debugging - cmd: npm test build: off version: "{build}" ================================================ FILE: binding.gyp ================================================ { "targets": [ { "target_name": "fontmanager", "sources": [ "src/FontManager.cc" ], "include_dirs" : [ " { ... }); */ export function getAvailableFonts(callback: (fonts: FontDescriptor[]) => void): void; /** * Queries all the fonts in the system matching the given parameters * * @param fontDescriptor Query parameters * @example * findFontsSync({ family: 'Arial' }); * findFontsSync(); * @returns All fonts descriptors matching query parameters */ export function findFontsSync(fontDescriptor: QueryFontDescriptor | undefined): FontDescriptor[]; /** * Queries all the fonts in the system matching the given parameters * * @param fontDescriptor Query parameters * @param callback Contains the font data * @example * findFonts({ family: 'Arial' }, (fonts) => { ... }); * findFonts((fonts) => { ... }); */ export function findFonts(fontDescriptor: QueryFontDescriptor | undefined, callback: (fonts: FontDescriptor[]) => void); /** * Find only one font matching the given query. This function always returns * a result (never null), so sometimes the output will not exactly match the * input font descriptor if not all input parameters could be met * * @param fontDescriptor Query parameters * @example * findFontSync({ family: 'Arial', weight: 700 }); * findFontSync(); * @returns Only one font description matching those query parameters */ export function findFontSync(fontDescriptor: QueryFontDescriptor): FontDescriptor; /** * Find only one font matching the given query. This function always returns * a result (never null), so sometimes the output will not exactly match the * input font descriptor if not all input parameters could be met * * @param fontDescriptor Query parameters * @example * findFont({ family: 'Arial', weight: 700 }, (font) => { ... }); * findFont((font) => { ... }); * @returns Only one font description matching those query parameters */ export function findFont(fontDescriptor: QueryFontDescriptor | undefined, callback: (font: FontDescriptor) => void); /** * Substitutes the font with the given post script name with another font * that contains the characters in text. If a font matching post script * name is not found, a fount containing the given characters is still * returned. If a font matching post script name is found, its * characteristics (bold, italic, etc) are used to find a suitable * replacement. If the font already contains the characters in text, it is * not replaced and the font descriptor for the original font is returned * * @param postscriptName Name of the font to be replaced * @param text Characters for matching * @returns Only one font description matching the function description */ export function substituteFontSync(postscriptName: string, text: string): FontDescriptor; /** * Substitutes the font with the given post script name with another font * that contains the characters in text. If a font matching post script * name is not found, a fount containing the given characters is still * returned. If a font matching post script name is found, its * characteristics (bold, italic, etc) are used to find a suitable * replacement. If the font already contains the characters in text, it is * not replaced and the font descriptor for the original font is returned * * @param postscriptName Name of the font to be replaced * @param text Characters for matching */ export function substituteFont(postscriptName: string, text: string, callback: (font: FontDescriptor) => void); } ================================================ FILE: package.json ================================================ { "name": "font-manager", "version": "0.3.1", "description": "Provides access to the system font catalog", "main": "build/Release/fontmanager", "types": "index.d.ts", "dependencies": { "nan": ">=2.10.0" }, "devDependencies": { "mocha": "*" }, "directories": { "test": "test" }, "scripts": { "test": "mocha" }, "repository": { "type": "git", "url": "https://github.com/devongovett/font-manager.git" }, "keywords": [ "font", "find", "search", "substitute", "enumerate" ], "author": "Devon Govett ", "license": "MIT", "gypfile": true, "bugs": { "url": "https://github.com/devongovett/font-manager/issues" }, "homepage": "https://github.com/devongovett/font-manager" } ================================================ FILE: src/FontDescriptor.h ================================================ #ifndef FONT_DESCRIPTOR_H #define FONT_DESCRIPTOR_H #include #include #include #include #include #include using namespace v8; enum FontWeight { FontWeightUndefined = 0, FontWeightThin = 100, FontWeightUltraLight = 200, FontWeightLight = 300, FontWeightNormal = 400, FontWeightMedium = 500, FontWeightSemiBold = 600, FontWeightBold = 700, FontWeightUltraBold = 800, FontWeightHeavy = 900 }; enum FontWidth { FontWidthUndefined = 0, FontWidthUltraCondensed = 1, FontWidthExtraCondensed = 2, FontWidthCondensed = 3, FontWidthSemiCondensed = 4, FontWidthNormal = 5, FontWidthSemiExpanded = 6, FontWidthExpanded = 7, FontWidthExtraExpanded = 8, FontWidthUltraExpanded = 9 }; struct FontDescriptor { public: const char *path; const char *postscriptName; const char *family; const char *style; FontWeight weight; FontWidth width; bool italic; bool monospace; FontDescriptor(Local obj) { path = NULL; postscriptName = getString(obj, "postscriptName"); family = getString(obj, "family"); style = getString(obj, "style"); weight = (FontWeight) getNumber(obj, "weight"); width = (FontWidth) getNumber(obj, "width"); italic = getBool(obj, "italic"); monospace = getBool(obj, "monospace"); } FontDescriptor() { path = NULL; postscriptName = NULL; family = NULL; style = NULL; weight = FontWeightUndefined; width = FontWidthUndefined; italic = false; monospace = false; } FontDescriptor(const char *path, const char *postscriptName, const char *family, const char *style, FontWeight weight, FontWidth width, bool italic, bool monospace) { this->path = copyString(path); this->postscriptName = copyString(postscriptName); this->family = copyString(family); this->style = copyString(style); this->weight = weight; this->width = width; this->italic = italic; this->monospace = monospace; } FontDescriptor(FontDescriptor *desc) { path = copyString(desc->path); postscriptName = copyString(desc->postscriptName); family = copyString(desc->family); style = copyString(desc->style); weight = desc->weight; width = desc->width; italic = desc->italic; monospace = desc->monospace; } ~FontDescriptor() { if (path) delete path; if (postscriptName) delete postscriptName; if (family) delete family; if (style) delete style; postscriptName = NULL; family = NULL; style = NULL; } Local toJSObject() { Nan::EscapableHandleScope scope; Local res = Nan::New(); if (path) { Nan::Set(res, Nan::New("path").ToLocalChecked(), Nan::New(path).ToLocalChecked()); } if (postscriptName) { Nan::Set(res, Nan::New("postscriptName").ToLocalChecked(), Nan::New(postscriptName).ToLocalChecked()); } if (family) { Nan::Set(res, Nan::New("family").ToLocalChecked(), Nan::New(family).ToLocalChecked()); } if (style) { Nan::Set(res, Nan::New("style").ToLocalChecked(), Nan::New(style).ToLocalChecked()); } Nan::Set(res, Nan::New("weight").ToLocalChecked(), Nan::New(weight)); Nan::Set(res, Nan::New("width").ToLocalChecked(), Nan::New(width)); Nan::Set(res, Nan::New("italic").ToLocalChecked(), Nan::New(italic)); Nan::Set(res, Nan::New("monospace").ToLocalChecked(), Nan::New(monospace)); return scope.Escape(res); } private: char *copyString(const char *input) { if (!input) return NULL; char *str = new char[strlen(input) + 1]; strcpy(str, input); return str; } char *getString(Local obj, const char *name) { Nan::HandleScope scope; MaybeLocal value = Nan::Get(obj, Nan::New(name).ToLocalChecked()); if (!value.IsEmpty() && value.ToLocalChecked()->IsString()) { return copyString(*Nan::Utf8String(value.ToLocalChecked())); } return NULL; } int getNumber(Local obj, const char *name) { Nan::HandleScope scope; MaybeLocal value = Nan::Get(obj, Nan::New(name).ToLocalChecked()); if (!value.IsEmpty() && value.ToLocalChecked()->IsNumber()) { return value.ToLocalChecked()->Int32Value(Nan::GetCurrentContext()).FromJust(); } return 0; } bool getBool(Local obj, const char *name) { Nan::HandleScope scope; MaybeLocal value = Nan::Get(obj, Nan::New(name).ToLocalChecked()); if (!value.IsEmpty() && value.ToLocalChecked()->IsBoolean()) { return value.ToLocalChecked()->BooleanValue(Nan::GetCurrentContext()).FromJust(); } return false; } }; class ResultSet : public std::vector { public: ~ResultSet() { for (ResultSet::iterator it = this->begin(); it != this->end(); it++) { delete *it; } } }; #endif ================================================ FILE: src/FontManager.cc ================================================ #include #include #include #include #include #include "FontDescriptor.h" using namespace v8; // these functions are implemented by the platform ResultSet *getAvailableFonts(); ResultSet *findFonts(FontDescriptor *); FontDescriptor *findFont(FontDescriptor *); FontDescriptor *substituteFont(char *, char *); // converts a ResultSet to a JavaScript array Local collectResults(ResultSet *results) { Nan::EscapableHandleScope scope; Local res = Nan::New(results->size()); int i = 0; for (ResultSet::iterator it = results->begin(); it != results->end(); it++) { Nan::Set(res, i++, (*it)->toJSObject()); } delete results; return scope.Escape(res); } // converts a FontDescriptor to a JavaScript object Local wrapResult(FontDescriptor *result) { Nan::EscapableHandleScope scope; if (result == NULL) return scope.Escape(Nan::Null()); Local res = result->toJSObject(); delete result; return scope.Escape(res); } // holds data about an operation that will be // performed on a background thread struct AsyncRequest { uv_work_t work; FontDescriptor *desc; // used by findFont and findFonts char *postscriptName; // used by substituteFont char *substitutionString; // ditto FontDescriptor *result; // for functions with a single result ResultSet *results; // for functions with multiple results Nan::Callback *callback; // the actual JS callback to call when we are done AsyncRequest(Local v) { work.data = (void *)this; callback = new Nan::Callback(v.As()); desc = NULL; postscriptName = NULL; substitutionString = NULL; result = NULL; results = NULL; } ~AsyncRequest() { delete callback; if (desc) delete desc; if (postscriptName) delete postscriptName; if (substitutionString) delete substitutionString; // result/results deleted by wrapResult/collectResults respectively } }; // calls the JavaScript callback for a request void asyncCallback(uv_work_t *work) { Nan::HandleScope scope; AsyncRequest *req = (AsyncRequest *) work->data; Nan::AsyncResource async("asyncCallback"); Local info[1]; if (req->results) { info[0] = collectResults(req->results); } else if (req->result) { info[0] = wrapResult(req->result); } else { info[0] = Nan::Null(); } req->callback->Call(1, info, &async); delete req; } void getAvailableFontsAsync(uv_work_t *work) { AsyncRequest *req = (AsyncRequest *) work->data; req->results = getAvailableFonts(); } template NAN_METHOD(getAvailableFonts) { if (async) { if (info.Length() < 1 || !info[0]->IsFunction()) return Nan::ThrowTypeError("Expected a callback"); AsyncRequest *req = new AsyncRequest(info[0]); uv_queue_work(uv_default_loop(), &req->work, getAvailableFontsAsync, (uv_after_work_cb) asyncCallback); return; } else { info.GetReturnValue().Set(collectResults(getAvailableFonts())); } } void findFontsAsync(uv_work_t *work) { AsyncRequest *req = (AsyncRequest *) work->data; req->results = findFonts(req->desc); } template NAN_METHOD(findFonts) { if (info.Length() < 1 || !info[0]->IsObject() || info[0]->IsFunction()) return Nan::ThrowTypeError("Expected a font descriptor"); Local desc = info[0].As(); FontDescriptor *descriptor = new FontDescriptor(desc); if (async) { if (info.Length() < 2 || !info[1]->IsFunction()) return Nan::ThrowTypeError("Expected a callback"); AsyncRequest *req = new AsyncRequest(info[1]); req->desc = descriptor; uv_queue_work(uv_default_loop(), &req->work, findFontsAsync, (uv_after_work_cb) asyncCallback); return; } else { Local res = collectResults(findFonts(descriptor)); delete descriptor; info.GetReturnValue().Set(res); } } void findFontAsync(uv_work_t *work) { AsyncRequest *req = (AsyncRequest *) work->data; req->result = findFont(req->desc); } template NAN_METHOD(findFont) { if (info.Length() < 1 || !info[0]->IsObject() || info[0]->IsFunction()) return Nan::ThrowTypeError("Expected a font descriptor"); Local desc = info[0].As(); FontDescriptor *descriptor = new FontDescriptor(desc); if (async) { if (info.Length() < 2 || !info[1]->IsFunction()) return Nan::ThrowTypeError("Expected a callback"); AsyncRequest *req = new AsyncRequest(info[1]); req->desc = descriptor; uv_queue_work(uv_default_loop(), &req->work, findFontAsync, (uv_after_work_cb) asyncCallback); return; } else { Local res = wrapResult(findFont(descriptor)); delete descriptor; info.GetReturnValue().Set(res); } } void substituteFontAsync(uv_work_t *work) { AsyncRequest *req = (AsyncRequest *) work->data; req->result = substituteFont(req->postscriptName, req->substitutionString); } template NAN_METHOD(substituteFont) { if (info.Length() < 1 || !info[0]->IsString()) return Nan::ThrowTypeError("Expected postscript name"); if (info.Length() < 2 || !info[1]->IsString()) return Nan::ThrowTypeError("Expected substitution string"); Nan::Utf8String postscriptName(info[0]); Nan::Utf8String substitutionString(info[1]); if (async) { if (info.Length() < 3 || !info[2]->IsFunction()) return Nan::ThrowTypeError("Expected a callback"); // copy the strings since the JS garbage collector might run before the async request is finished char *ps = new char[postscriptName.length() + 1]; strcpy(ps, *postscriptName); char *sub = new char[substitutionString.length() + 1]; strcpy(sub, *substitutionString); AsyncRequest *req = new AsyncRequest(info[2]); req->postscriptName = ps; req->substitutionString = sub; uv_queue_work(uv_default_loop(), &req->work, substituteFontAsync, (uv_after_work_cb) asyncCallback); return; } else { info.GetReturnValue().Set(wrapResult(substituteFont(*postscriptName, *substitutionString))); } } NAN_MODULE_INIT(Init) { Nan::Export(target, "getAvailableFonts", getAvailableFonts); Nan::Export(target, "getAvailableFontsSync", getAvailableFonts); Nan::Export(target, "findFonts", findFonts); Nan::Export(target, "findFontsSync", findFonts); Nan::Export(target, "findFont", findFont); Nan::Export(target, "findFontSync", findFont); Nan::Export(target, "substituteFont", substituteFont); Nan::Export(target, "substituteFontSync", substituteFont); } NODE_MODULE(fontmanager, Init) ================================================ FILE: src/FontManagerLinux.cc ================================================ #include #include "FontDescriptor.h" int convertWeight(FontWeight weight) { switch (weight) { case FontWeightThin: return FC_WEIGHT_THIN; case FontWeightUltraLight: return FC_WEIGHT_ULTRALIGHT; case FontWeightLight: return FC_WEIGHT_LIGHT; case FontWeightNormal: return FC_WEIGHT_REGULAR; case FontWeightMedium: return FC_WEIGHT_MEDIUM; case FontWeightSemiBold: return FC_WEIGHT_SEMIBOLD; case FontWeightBold: return FC_WEIGHT_BOLD; case FontWeightUltraBold: return FC_WEIGHT_EXTRABOLD; case FontWeightHeavy: return FC_WEIGHT_ULTRABLACK; default: return FC_WEIGHT_REGULAR; } } FontWeight convertWeight(int weight) { switch (weight) { case FC_WEIGHT_THIN: return FontWeightThin; case FC_WEIGHT_ULTRALIGHT: return FontWeightUltraLight; case FC_WEIGHT_LIGHT: return FontWeightLight; case FC_WEIGHT_REGULAR: return FontWeightNormal; case FC_WEIGHT_MEDIUM: return FontWeightMedium; case FC_WEIGHT_SEMIBOLD: return FontWeightSemiBold; case FC_WEIGHT_BOLD: return FontWeightBold; case FC_WEIGHT_EXTRABOLD: return FontWeightUltraBold; case FC_WEIGHT_ULTRABLACK: return FontWeightHeavy; default: return FontWeightNormal; } } int convertWidth(FontWidth width) { switch (width) { case FontWidthUltraCondensed: return FC_WIDTH_ULTRACONDENSED; case FontWidthExtraCondensed: return FC_WIDTH_EXTRACONDENSED; case FontWidthCondensed: return FC_WIDTH_CONDENSED; case FontWidthSemiCondensed: return FC_WIDTH_SEMICONDENSED; case FontWidthNormal: return FC_WIDTH_NORMAL; case FontWidthSemiExpanded: return FC_WIDTH_SEMIEXPANDED; case FontWidthExpanded: return FC_WIDTH_EXPANDED; case FontWidthExtraExpanded: return FC_WIDTH_EXTRAEXPANDED; case FontWidthUltraExpanded: return FC_WIDTH_ULTRAEXPANDED; default: return FC_WIDTH_NORMAL; } } FontWidth convertWidth(int width) { switch (width) { case FC_WIDTH_ULTRACONDENSED: return FontWidthUltraCondensed; case FC_WIDTH_EXTRACONDENSED: return FontWidthExtraCondensed; case FC_WIDTH_CONDENSED: return FontWidthCondensed; case FC_WIDTH_SEMICONDENSED: return FontWidthSemiCondensed; case FC_WIDTH_NORMAL: return FontWidthNormal; case FC_WIDTH_SEMIEXPANDED: return FontWidthSemiExpanded; case FC_WIDTH_EXPANDED: return FontWidthExpanded; case FC_WIDTH_EXTRAEXPANDED: return FontWidthExtraExpanded; case FC_WIDTH_ULTRAEXPANDED: return FontWidthUltraExpanded; default: return FontWidthNormal; } } FontDescriptor *createFontDescriptor(FcPattern *pattern) { FcChar8 *path, *psName, *family, *style; int weight, width, slant, spacing; FcPatternGetString(pattern, FC_FILE, 0, &path); FcPatternGetString(pattern, FC_POSTSCRIPT_NAME, 0, &psName); FcPatternGetString(pattern, FC_FAMILY, 0, &family); FcPatternGetString(pattern, FC_STYLE, 0, &style); FcPatternGetInteger(pattern, FC_WEIGHT, 0, &weight); FcPatternGetInteger(pattern, FC_WIDTH, 0, &width); FcPatternGetInteger(pattern, FC_SLANT, 0, &slant); FcPatternGetInteger(pattern, FC_SPACING, 0, &spacing); return new FontDescriptor( (char *) path, (char *) psName, (char *) family, (char *) style, convertWeight(weight), convertWidth(width), slant == FC_SLANT_ITALIC, spacing == FC_MONO ); } ResultSet *getResultSet(FcFontSet *fs) { ResultSet *res = new ResultSet(); if (!fs) return res; for (int i = 0; i < fs->nfont; i++) { res->push_back(createFontDescriptor(fs->fonts[i])); } return res; } ResultSet *getAvailableFonts() { FcInit(); FcPattern *pattern = FcPatternCreate(); FcObjectSet *os = FcObjectSetBuild(FC_FILE, FC_POSTSCRIPT_NAME, FC_FAMILY, FC_STYLE, FC_WEIGHT, FC_WIDTH, FC_SLANT, FC_SPACING, NULL); FcFontSet *fs = FcFontList(NULL, pattern, os); ResultSet *res = getResultSet(fs); FcPatternDestroy(pattern); FcObjectSetDestroy(os); FcFontSetDestroy(fs); return res; } FcPattern *createPattern(FontDescriptor *desc) { FcInit(); FcPattern *pattern = FcPatternCreate(); if (desc->postscriptName) FcPatternAddString(pattern, FC_POSTSCRIPT_NAME, (FcChar8 *) desc->postscriptName); if (desc->family) FcPatternAddString(pattern, FC_FAMILY, (FcChar8 *) desc->family); if (desc->style) FcPatternAddString(pattern, FC_STYLE, (FcChar8 *) desc->style); if (desc->italic) FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC); if (desc->weight) FcPatternAddInteger(pattern, FC_WEIGHT, convertWeight(desc->weight)); if (desc->width) FcPatternAddInteger(pattern, FC_WIDTH, convertWidth(desc->width)); if (desc->monospace) FcPatternAddInteger(pattern, FC_SPACING, FC_MONO); return pattern; } ResultSet *findFonts(FontDescriptor *desc) { FcPattern *pattern = createPattern(desc); FcObjectSet *os = FcObjectSetBuild(FC_FILE, FC_POSTSCRIPT_NAME, FC_FAMILY, FC_STYLE, FC_WEIGHT, FC_WIDTH, FC_SLANT, FC_SPACING, NULL); FcFontSet *fs = FcFontList(NULL, pattern, os); ResultSet *res = getResultSet(fs); FcFontSetDestroy(fs); FcPatternDestroy(pattern); FcObjectSetDestroy(os); return res; } FontDescriptor *findFont(FontDescriptor *desc) { FcPattern *pattern = createPattern(desc); FcConfigSubstitute(NULL, pattern, FcMatchPattern); FcDefaultSubstitute(pattern); FcResult result; FcPattern *font = FcFontMatch(NULL, pattern, &result); FontDescriptor *res = createFontDescriptor(font); FcPatternDestroy(pattern); FcPatternDestroy(font); return res; } FontDescriptor *substituteFont(char *postscriptName, char *string) { FcInit(); // create a pattern with the postscript name FcPattern* pattern = FcPatternCreate(); FcPatternAddString(pattern, FC_POSTSCRIPT_NAME, (FcChar8 *) postscriptName); // create a charset with each character in the string FcCharSet* charset = FcCharSetCreate(); int len = strlen(string); for (int i = 0; i < len;) { FcChar32 c; i += FcUtf8ToUcs4((FcChar8 *)string + i, &c, len - i); FcCharSetAddChar(charset, c); } FcPatternAddCharSet(pattern, FC_CHARSET, charset); FcCharSetDestroy(charset); FcConfigSubstitute(0, pattern, FcMatchPattern); FcDefaultSubstitute(pattern); // find the best match font FcResult result; FcPattern *font = FcFontMatch(NULL, pattern, &result); FontDescriptor *res = createFontDescriptor(font); FcPatternDestroy(pattern); FcPatternDestroy(font); return res; } ================================================ FILE: src/FontManagerMac.mm ================================================ #include #include #include "FontDescriptor.h" // converts a CoreText weight (-1 to +1) to a standard weight (100 to 900) static int convertWeight(float weight) { if (weight <= -0.8f) return 100; else if (weight <= -0.6f) return 200; else if (weight <= -0.4f) return 300; else if (weight <= 0.0f) return 400; else if (weight <= 0.25f) return 500; else if (weight <= 0.35f) return 600; else if (weight <= 0.4f) return 700; else if (weight <= 0.6f) return 800; else return 900; } // converts a CoreText width (-1 to +1) to a standard width (1 to 9) static int convertWidth(float unit) { if (unit < 0) { return 1 + (1 + unit) * 4; } else { return 5 + unit * 4; } } FontDescriptor *createFontDescriptor(CTFontDescriptorRef descriptor) { NSURL *url = (NSURL *) CTFontDescriptorCopyAttribute(descriptor, kCTFontURLAttribute); NSString *psName = (NSString *) CTFontDescriptorCopyAttribute(descriptor, kCTFontNameAttribute); NSString *family = (NSString *) CTFontDescriptorCopyAttribute(descriptor, kCTFontFamilyNameAttribute); NSString *style = (NSString *) CTFontDescriptorCopyAttribute(descriptor, kCTFontStyleNameAttribute); NSDictionary *traits = (NSDictionary *) CTFontDescriptorCopyAttribute(descriptor, kCTFontTraitsAttribute); NSNumber *weightVal = traits[(id)kCTFontWeightTrait]; FontWeight weight = (FontWeight) convertWeight([weightVal floatValue]); NSNumber *widthVal = traits[(id)kCTFontWidthTrait]; FontWidth width = (FontWidth) convertWidth([widthVal floatValue]); NSNumber *symbolicTraitsVal = traits[(id)kCTFontSymbolicTrait]; unsigned int symbolicTraits = [symbolicTraitsVal unsignedIntValue]; FontDescriptor *res = new FontDescriptor( [[url path] UTF8String], [psName UTF8String], [family UTF8String], [style UTF8String], weight, width, (symbolicTraits & kCTFontItalicTrait) != 0, (symbolicTraits & kCTFontMonoSpaceTrait) != 0 ); [url release]; [psName release]; [family release]; [style release]; [traits release]; return res; } ResultSet *getAvailableFonts() { // cache font collection for fast use in future calls static CTFontCollectionRef collection = NULL; if (collection == NULL) collection = CTFontCollectionCreateFromAvailableFonts(NULL); NSArray *matches = (NSArray *) CTFontCollectionCreateMatchingFontDescriptors(collection); ResultSet *results = new ResultSet(); for (id m in matches) { CTFontDescriptorRef match = (CTFontDescriptorRef) m; results->push_back(createFontDescriptor(match)); } [matches release]; return results; } // helper to square a value static inline int sqr(int value) { return value * value; } CTFontDescriptorRef getFontDescriptor(FontDescriptor *desc) { // build a dictionary of font attributes NSMutableDictionary *attrs = [NSMutableDictionary dictionary]; CTFontSymbolicTraits symbolicTraits = 0; if (desc->postscriptName) { NSString *postscriptName = [NSString stringWithUTF8String:desc->postscriptName]; attrs[(id)kCTFontNameAttribute] = postscriptName; } if (desc->family) { NSString *family = [NSString stringWithUTF8String:desc->family]; attrs[(id)kCTFontFamilyNameAttribute] = family; } if (desc->style) { NSString *style = [NSString stringWithUTF8String:desc->style]; attrs[(id)kCTFontStyleNameAttribute] = style; } // build symbolic traits if (desc->italic) symbolicTraits |= kCTFontItalicTrait; if (desc->weight == FontWeightBold) symbolicTraits |= kCTFontBoldTrait; if (desc->monospace) symbolicTraits |= kCTFontMonoSpaceTrait; if (desc->width == FontWidthCondensed) symbolicTraits |= kCTFontCondensedTrait; if (desc->width == FontWidthExpanded) symbolicTraits |= kCTFontExpandedTrait; if (symbolicTraits) { NSDictionary *traits = @{(id)kCTFontSymbolicTrait:[NSNumber numberWithUnsignedInt:symbolicTraits]}; attrs[(id)kCTFontTraitsAttribute] = traits; } // create a font descriptor and search for matches return CTFontDescriptorCreateWithAttributes((CFDictionaryRef) attrs); } int metricForMatch(CTFontDescriptorRef match, FontDescriptor *desc) { NSDictionary *dict = (NSDictionary *)CTFontDescriptorCopyAttribute(match, kCTFontTraitsAttribute); bool italic = ([dict[(id)kCTFontSymbolicTrait] unsignedIntValue] & kCTFontItalicTrait); // normalize everything to base-900 int metric = 0; if (desc->weight) metric += sqr(convertWeight([dict[(id)kCTFontWeightTrait] floatValue]) - desc->weight); if (desc->width) metric += sqr((convertWidth([dict[(id)kCTFontWidthTrait] floatValue]) - desc->width) * 100); metric += sqr((italic != desc->italic) * 900); [dict release]; return metric; } ResultSet *findFonts(FontDescriptor *desc) { CTFontDescriptorRef descriptor = getFontDescriptor(desc); NSArray *matches = (NSArray *) CTFontDescriptorCreateMatchingFontDescriptors(descriptor, NULL); ResultSet *results = new ResultSet(); NSArray *sorted = [matches sortedArrayUsingComparator:^NSComparisonResult(id a, id b) { int ma = metricForMatch((CTFontDescriptorRef) a, desc); int mb = metricForMatch((CTFontDescriptorRef) b, desc); return ma < mb ? NSOrderedAscending : ma > mb ? NSOrderedDescending : NSOrderedSame; }]; for (id m in sorted) { CTFontDescriptorRef match = (CTFontDescriptorRef) m; int mb = metricForMatch((CTFontDescriptorRef) m, desc); if (mb < 10000) { results->push_back(createFontDescriptor(match)); } } CFRelease(descriptor); [matches release]; return results; } CTFontDescriptorRef findBest(FontDescriptor *desc, NSArray *matches) { // find the closest match for width and weight attributes CTFontDescriptorRef best = NULL; int bestMetric = INT_MAX; for (id m in matches) { int metric = metricForMatch((CTFontDescriptorRef) m, desc); if (metric < bestMetric) { bestMetric = metric; best = (CTFontDescriptorRef) m; } // break if this is an exact match if (metric == 0) break; } return best; } FontDescriptor *findFont(FontDescriptor *desc) { FontDescriptor *res = NULL; CTFontDescriptorRef descriptor = getFontDescriptor(desc); NSArray *matches = (NSArray *) CTFontDescriptorCreateMatchingFontDescriptors(descriptor, NULL); // if there was no match, try again but only try to match traits if ([matches count] == 0) { [matches release]; NSSet *set = [NSSet setWithObjects:(id)kCTFontTraitsAttribute, nil]; matches = (NSArray *) CTFontDescriptorCreateMatchingFontDescriptors(descriptor, (CFSetRef) set); } // find the closest match for width and weight attributes CTFontDescriptorRef best = findBest(desc, matches); // if we found a match, generate and return a URL for it if (best) { res = createFontDescriptor(best); } [matches release]; CFRelease(descriptor); return res; } FontDescriptor *substituteFont(char *postscriptName, char *string) { FontDescriptor *res = NULL; // create a font descriptor to find the font by its postscript name // we don't use CTFontCreateWithName because that supports font // names other than the postscript name but prints warnings. NSString *ps = [NSString stringWithUTF8String:postscriptName]; NSDictionary *attrs = @{(id)kCTFontNameAttribute: ps}; CTFontDescriptorRef descriptor = CTFontDescriptorCreateWithAttributes((CFDictionaryRef) attrs); CTFontRef font = CTFontCreateWithFontDescriptor(descriptor, 12.0, NULL); // find a substitute font that support the given characters NSString *str = [NSString stringWithUTF8String:string]; CTFontRef substituteFont = CTFontCreateForString(font, (CFStringRef) str, CFRangeMake(0, [str length])); CTFontDescriptorRef substituteDescriptor = CTFontCopyFontDescriptor(substituteFont); // finally, create and return a result object for this substitute font res = createFontDescriptor(substituteDescriptor); CFRelease(font); CFRelease(substituteFont); CFRelease(substituteDescriptor); return res; } ================================================ FILE: src/FontManagerWindows.cc ================================================ #define WINVER 0x0600 #include "FontDescriptor.h" #include #include #include // throws a JS error when there is some exception in DirectWrite #define HR(hr) \ if (FAILED(hr)) throw "Font loading error"; WCHAR *utf8ToUtf16(const char *input) { unsigned int len = MultiByteToWideChar(CP_UTF8, 0, input, -1, NULL, 0); WCHAR *output = new WCHAR[len]; MultiByteToWideChar(CP_UTF8, 0, input, -1, output, len); return output; } char *utf16ToUtf8(const WCHAR *input) { unsigned int len = WideCharToMultiByte(CP_UTF8, 0, input, -1, NULL, 0, NULL, NULL); char *output = new char[len]; WideCharToMultiByte(CP_UTF8, 0, input, -1, output, len, NULL, NULL); return output; } // returns the index of the user's locale in the set of localized strings unsigned int getLocaleIndex(IDWriteLocalizedStrings *strings) { unsigned int index = 0; BOOL exists = false; wchar_t localeName[LOCALE_NAME_MAX_LENGTH]; // Get the default locale for this user. int success = GetUserDefaultLocaleName(localeName, LOCALE_NAME_MAX_LENGTH); // If the default locale is returned, find that locale name, otherwise use "en-us". if (success) { HR(strings->FindLocaleName(localeName, &index, &exists)); } // if the above find did not find a match, retry with US English if (!exists) { HR(strings->FindLocaleName(L"en-us", &index, &exists)); } if (!exists) index = 0; return index; } // gets a localized string for a font char *getString(IDWriteFont *font, DWRITE_INFORMATIONAL_STRING_ID string_id) { char *res = NULL; IDWriteLocalizedStrings *strings = NULL; BOOL exists = false; HR(font->GetInformationalStrings( string_id, &strings, &exists )); if (exists) { unsigned int index = getLocaleIndex(strings); unsigned int len = 0; WCHAR *str = NULL; HR(strings->GetStringLength(index, &len)); str = new WCHAR[len + 1]; HR(strings->GetString(index, str, len + 1)); // convert to utf8 res = utf16ToUtf8(str); delete str; strings->Release(); } if (!res) { res = new char[1]; res[0] = '\0'; } return res; } FontDescriptor *resultFromFont(IDWriteFont *font) { FontDescriptor *res = NULL; IDWriteFontFace *face = NULL; unsigned int numFiles = 0; HR(font->CreateFontFace(&face)); // get the font files from this font face IDWriteFontFile *files = NULL; HR(face->GetFiles(&numFiles, NULL)); HR(face->GetFiles(&numFiles, &files)); // return the first one if (numFiles > 0) { IDWriteFontFileLoader *loader = NULL; IDWriteLocalFontFileLoader *fileLoader = NULL; unsigned int nameLength = 0; const void *referenceKey = NULL; unsigned int referenceKeySize = 0; WCHAR *name = NULL; HR(files[0].GetLoader(&loader)); // check if this is a local font file HRESULT hr = loader->QueryInterface(__uuidof(IDWriteLocalFontFileLoader), (void **)&fileLoader); if (SUCCEEDED(hr)) { // get the file path HR(files[0].GetReferenceKey(&referenceKey, &referenceKeySize)); HR(fileLoader->GetFilePathLengthFromKey(referenceKey, referenceKeySize, &nameLength)); name = new WCHAR[nameLength + 1]; HR(fileLoader->GetFilePathFromKey(referenceKey, referenceKeySize, name, nameLength + 1)); char *psName = utf16ToUtf8(name); char *postscriptName = getString(font, DWRITE_INFORMATIONAL_STRING_POSTSCRIPT_NAME); char *family = getString(font, DWRITE_INFORMATIONAL_STRING_WIN32_FAMILY_NAMES); char *style = getString(font, DWRITE_INFORMATIONAL_STRING_WIN32_SUBFAMILY_NAMES); bool monospace = false; // this method requires windows 7, so we need to cast to an IDWriteFontFace1 IDWriteFontFace1 *face1; HRESULT hr = face->QueryInterface(__uuidof(IDWriteFontFace1), (void **)&face1); if (SUCCEEDED(hr)) { monospace = face1->IsMonospacedFont() == TRUE; } res = new FontDescriptor( psName, postscriptName, family, style, (FontWeight) font->GetWeight(), (FontWidth) font->GetStretch(), font->GetStyle() == DWRITE_FONT_STYLE_ITALIC, monospace ); delete psName; delete name; delete postscriptName; delete family; delete style; fileLoader->Release(); } loader->Release(); } face->Release(); files->Release(); return res; } ResultSet *getAvailableFonts() { ResultSet *res = new ResultSet(); int count = 0; IDWriteFactory *factory = NULL; HR(DWriteCreateFactory( DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), reinterpret_cast(&factory) )); // Get the system font collection. IDWriteFontCollection *collection = NULL; HR(factory->GetSystemFontCollection(&collection)); // Get the number of font families in the collection. int familyCount = collection->GetFontFamilyCount(); // track postscript names we've already added // using a set so we don't get any duplicates. std::unordered_set psNames; for (int i = 0; i < familyCount; i++) { IDWriteFontFamily *family = NULL; // Get the font family. HR(collection->GetFontFamily(i, &family)); int fontCount = family->GetFontCount(); for (int j = 0; j < fontCount; j++) { IDWriteFont *font = NULL; HR(family->GetFont(j, &font)); FontDescriptor *result = resultFromFont(font); if (psNames.count(result->postscriptName) == 0) { res->push_back(resultFromFont(font)); psNames.insert(result->postscriptName); } } family->Release(); } collection->Release(); factory->Release(); return res; } bool resultMatches(FontDescriptor *result, FontDescriptor *desc) { if (desc->postscriptName && strcmp(desc->postscriptName, result->postscriptName) != 0) return false; if (desc->family && strcmp(desc->family, result->family) != 0) return false; if (desc->style && strcmp(desc->style, result->style) != 0) return false; if (desc->weight && desc->weight != result->weight) return false; if (desc->width && desc->width != result->width) return false; if (desc->italic != result->italic) return false; if (desc->monospace != result->monospace) return false; return true; } ResultSet *findFonts(FontDescriptor *desc) { ResultSet *fonts = getAvailableFonts(); for (ResultSet::iterator it = fonts->begin(); it != fonts->end();) { if (!resultMatches(*it, desc)) { delete *it; it = fonts->erase(it); } else { it++; } } return fonts; } FontDescriptor *findFont(FontDescriptor *desc) { ResultSet *fonts = findFonts(desc); // if we didn't find anything, try again with only the font traits, no string names if (fonts->size() == 0) { delete fonts; FontDescriptor *fallback = new FontDescriptor( NULL, NULL, NULL, NULL, desc->weight, desc->width, desc->italic, false ); fonts = findFonts(fallback); } // ok, nothing. shouldn't happen often. // just return the first available font if (fonts->size() == 0) { delete fonts; fonts = getAvailableFonts(); } // hopefully we found something now. // copy and return the first result if (fonts->size() > 0) { FontDescriptor *res = new FontDescriptor(fonts->front()); delete fonts; return res; } // whoa, weird. no fonts installed or something went wrong. delete fonts; return NULL; } // custom text renderer used to determine the fallback font for a given char class FontFallbackRenderer : public IDWriteTextRenderer { public: IDWriteFontCollection *systemFonts; IDWriteFont *font; unsigned long refCount; FontFallbackRenderer(IDWriteFontCollection *collection) { refCount = 0; collection->AddRef(); systemFonts = collection; font = NULL; } ~FontFallbackRenderer() { if (systemFonts) systemFonts->Release(); if (font) font->Release(); } // IDWriteTextRenderer methods IFACEMETHOD(DrawGlyphRun)( void *clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, DWRITE_MEASURING_MODE measuringMode, DWRITE_GLYPH_RUN const *glyphRun, DWRITE_GLYPH_RUN_DESCRIPTION const *glyphRunDescription, IUnknown *clientDrawingEffect) { // save the font that was actually rendered return systemFonts->GetFontFromFontFace(glyphRun->fontFace, &font); } IFACEMETHOD(DrawUnderline)( void *clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, DWRITE_UNDERLINE const *underline, IUnknown *clientDrawingEffect) { return E_NOTIMPL; } IFACEMETHOD(DrawStrikethrough)( void *clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, DWRITE_STRIKETHROUGH const *strikethrough, IUnknown *clientDrawingEffect) { return E_NOTIMPL; } IFACEMETHOD(DrawInlineObject)( void *clientDrawingContext, FLOAT originX, FLOAT originY, IDWriteInlineObject *inlineObject, BOOL isSideways, BOOL isRightToLeft, IUnknown *clientDrawingEffect) { return E_NOTIMPL; } // IDWritePixelSnapping methods IFACEMETHOD(IsPixelSnappingDisabled)(void *clientDrawingContext, BOOL *isDisabled) { *isDisabled = FALSE; return S_OK; } IFACEMETHOD(GetCurrentTransform)(void *clientDrawingContext, DWRITE_MATRIX *transform) { const DWRITE_MATRIX ident = {1.0, 0.0, 0.0, 1.0, 0.0, 0.0}; *transform = ident; return S_OK; } IFACEMETHOD(GetPixelsPerDip)(void *clientDrawingContext, FLOAT *pixelsPerDip) { *pixelsPerDip = 1.0f; return S_OK; } // IUnknown methods IFACEMETHOD_(unsigned long, AddRef)() { return InterlockedIncrement(&refCount); } IFACEMETHOD_(unsigned long, Release)() { unsigned long newCount = InterlockedDecrement(&refCount); if (newCount == 0) { delete this; return 0; } return newCount; } IFACEMETHOD(QueryInterface)(IID const& riid, void **ppvObject) { if (__uuidof(IDWriteTextRenderer) == riid) { *ppvObject = this; } else if (__uuidof(IDWritePixelSnapping) == riid) { *ppvObject = this; } else if (__uuidof(IUnknown) == riid) { *ppvObject = this; } else { *ppvObject = nullptr; return E_FAIL; } this->AddRef(); return S_OK; } }; FontDescriptor *substituteFont(char *postscriptName, char *string) { FontDescriptor *res = NULL; IDWriteFactory *factory = NULL; HR(DWriteCreateFactory( DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), reinterpret_cast(&factory) )); // Get the system font collection. IDWriteFontCollection *collection = NULL; HR(factory->GetSystemFontCollection(&collection)); // find the font for the given postscript name FontDescriptor *desc = new FontDescriptor(); desc->postscriptName = postscriptName; FontDescriptor *font = findFont(desc); // create a text format object for this font IDWriteTextFormat *format = NULL; if (font) { WCHAR *familyName = utf8ToUtf16(font->family); // create a text format HR(factory->CreateTextFormat( familyName, collection, (DWRITE_FONT_WEIGHT) font->weight, font->italic ? DWRITE_FONT_STYLE_ITALIC : DWRITE_FONT_STYLE_NORMAL, (DWRITE_FONT_STRETCH) font->width, 12.0, L"en-us", &format )); delete familyName; delete font; } else { // this should never happen, but just in case, let the system // decide the default font in case findFont returned nothing. HR(factory->CreateTextFormat( L"", collection, DWRITE_FONT_WEIGHT_REGULAR, DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, 12.0, L"en-us", &format )); } // convert utf8 string for substitution to utf16 WCHAR *str = utf8ToUtf16(string); // create a text layout for the substitution string IDWriteTextLayout *layout = NULL; HR(factory->CreateTextLayout( str, wcslen(str), format, 100.0, 100.0, &layout )); // render it using a custom renderer that saves the physical font being used FontFallbackRenderer *renderer = new FontFallbackRenderer(collection); HR(layout->Draw(NULL, renderer, 100.0, 100.0)); // if we found something, create a result object if (renderer->font) { res = resultFromFont(renderer->font); } // free all the things delete renderer; layout->Release(); format->Release(); desc->postscriptName = NULL; delete desc; delete str; collection->Release(); factory->Release(); return res; } ================================================ FILE: test/index.js ================================================ var fontManager = require('../'); var assert = require('assert'); // some standard fonts that are likely to be installed on the platform the tests are running on var standardFont = process.platform === 'linux' ? 'Liberation Sans' : 'Arial'; var postscriptName = process.platform === 'linux' ? 'LiberationSans' : 'ArialMT'; describe('font-manager', function() { it('should export some functions', function() { assert.equal(typeof fontManager.getAvailableFonts, 'function'); assert.equal(typeof fontManager.getAvailableFontsSync, 'function'); assert.equal(typeof fontManager.findFonts, 'function'); assert.equal(typeof fontManager.findFontsSync, 'function'); assert.equal(typeof fontManager.findFont, 'function'); assert.equal(typeof fontManager.findFontSync, 'function'); assert.equal(typeof fontManager.substituteFont, 'function'); assert.equal(typeof fontManager.substituteFontSync, 'function'); }); function assertFontDescriptor(font) { assert.equal(typeof font, 'object'); assert.equal(typeof font.path, 'string'); assert.equal(typeof font.postscriptName, 'string'); assert.equal(typeof font.family, 'string'); assert.equal(typeof font.style, 'string'); assert.equal(typeof font.weight, 'number'); assert.equal(typeof font.width, 'number'); assert.equal(typeof font.italic, 'boolean'); assert.equal(typeof font.monospace, 'boolean'); } describe('getAvailableFonts', function() { it('should throw if no callback is provided', function() { assert.throws(function() { fontManager.getAvailableFonts(); }, /Expected a callback/); }); it('should throw if callback is not a function', function() { assert.throws(function() { fontManager.getAvailableFonts(2); }, /Expected a callback/); }); it('should getAvailableFonts asynchronously', function(done) { var async = false; fontManager.getAvailableFonts(function(fonts) { assert(async); assert(Array.isArray(fonts)); assert(fonts.length > 0); fonts.forEach(assertFontDescriptor); done(); }); async = true; }); }); describe('getAvailableFontsSync', function() { it('should getAvailableFonts synchronously', function() { var fonts = fontManager.getAvailableFontsSync(); assert(Array.isArray(fonts)); assert(fonts.length > 0); fonts.forEach(assertFontDescriptor); }); }); describe('findFonts', function() { it('should throw if no font descriptor is provided', function() { assert.throws(function() { fontManager.findFonts(function(fonts) {}); }, /Expected a font descriptor/); }); it('should throw if font descriptor is not an object', function() { assert.throws(function() { fontManager.findFonts(2, function(fonts) {}); }, /Expected a font descriptor/); }); it('should throw if no callback is provided', function() { assert.throws(function() { fontManager.findFonts({ family: standardFont }); }, /Expected a callback/); }); it('should throw if callback is not a function', function() { assert.throws(function() { fontManager.findFonts({ family: standardFont }, 2); }, /Expected a callback/); }); it('should findFonts asynchronously', function(done) { var async = false; fontManager.findFonts({ family: standardFont }, function(fonts) { assert(async); assert(Array.isArray(fonts)); assert(fonts.length > 0); fonts.forEach(assertFontDescriptor); done(); }); async = true; }); it('should find fonts by postscriptName', function(done) { fontManager.findFonts({ postscriptName: postscriptName }, function(fonts) { assert(Array.isArray(fonts)); assert.equal(fonts.length, 1); fonts.forEach(assertFontDescriptor); assert.equal(fonts[0].postscriptName, postscriptName); assert.equal(fonts[0].family, standardFont); done(); }); }); it('should find fonts by family and style', function(done) { fontManager.findFonts({ family: standardFont, style: 'Bold' }, function(fonts) { assert(Array.isArray(fonts)); assert.equal(fonts.length, 1); fonts.forEach(assertFontDescriptor); assert.equal(fonts[0].family, standardFont); assert.equal(fonts[0].style, 'Bold'); assert.equal(fonts[0].weight, 700); done(); }); }); it('should find fonts by weight', function(done) { fontManager.findFonts({ family: standardFont, weight: 700 }, function(fonts) { assert(Array.isArray(fonts)); assert(fonts.length > 0); fonts.forEach(assertFontDescriptor); fonts.forEach(function(font) { assert.equal(font.weight, 700); }); done(); }); }); it('should find italic fonts', function(done) { fontManager.findFonts({ family: standardFont, italic: true }, function(fonts) { assert(Array.isArray(fonts)); assert(fonts.length > 0); fonts.forEach(assertFontDescriptor); fonts.forEach(function(font) { assert.equal(font.italic, true); }); done(); }); }); it('should find italic and bold fonts', function(done) { fontManager.findFonts({ family: standardFont, italic: true, weight: 700 }, function(fonts) { assert(Array.isArray(fonts)); assert(fonts.length > 0); fonts.forEach(assertFontDescriptor); fonts.forEach(function(font) { assert.equal(font.italic, true); assert.equal(font.weight, 700); }); done(); }); }); it('should return an empty array for nonexistent family', function(done) { fontManager.findFonts({ family: '' + Date.now() }, function(fonts) { assert(Array.isArray(fonts)); assert.equal(fonts.length, 0); done(); }); }); it('should return an empty array for nonexistent postscriptName', function(done) { fontManager.findFonts({ postscriptName: '' + Date.now() }, function(fonts) { assert(Array.isArray(fonts)); assert.equal(fonts.length, 0); done(); }); }); it('should return many fonts for empty font descriptor', function(done) { fontManager.findFonts({}, function(fonts) { assert(Array.isArray(fonts)); assert(fonts.length > 0); fonts.forEach(assertFontDescriptor); done(); }); }); }); describe('findFontsSync', function() { it('should throw if no font descriptor is provided', function() { assert.throws(function() { fontManager.findFontsSync(); }, /Expected a font descriptor/); }); it('should throw if font descriptor is not an object', function() { assert.throws(function() { fontManager.findFontsSync(2); }, /Expected a font descriptor/); }); it('should findFonts synchronously', function() { var fonts = fontManager.findFontsSync({ family: standardFont }); assert(Array.isArray(fonts)); assert(fonts.length > 0); fonts.forEach(assertFontDescriptor); }); it('should find fonts by postscriptName', function() { var fonts = fontManager.findFontsSync({ postscriptName: postscriptName }); assert(Array.isArray(fonts)); assert.equal(fonts.length, 1); fonts.forEach(assertFontDescriptor); assert.equal(fonts[0].postscriptName, postscriptName); assert.equal(fonts[0].family, standardFont); }); it('should find fonts by family and style', function() { var fonts = fontManager.findFontsSync({ family: standardFont, style: 'Bold' }); assert(Array.isArray(fonts)); assert.equal(fonts.length, 1); fonts.forEach(assertFontDescriptor); assert.equal(fonts[0].family, standardFont); assert.equal(fonts[0].style, 'Bold'); assert.equal(fonts[0].weight, 700); }); it('should find fonts by weight', function() { var fonts = fontManager.findFontsSync({ family: standardFont, weight: 700 }); assert(Array.isArray(fonts)); assert(fonts.length > 0); fonts.forEach(assertFontDescriptor); assert.equal(fonts[0].weight, 700); }); it('should find italic fonts', function() { var fonts = fontManager.findFontsSync({ family: standardFont, italic: true }); assert(Array.isArray(fonts)); assert(fonts.length > 0); fonts.forEach(assertFontDescriptor); assert.equal(fonts[0].italic, true); }); it('should find italic and bold fonts', function() { var fonts = fontManager.findFontsSync({ family: standardFont, italic: true, weight: 700 }); assert(Array.isArray(fonts)); assert(fonts.length > 0); fonts.forEach(assertFontDescriptor); assert.equal(fonts[0].italic, true); assert.equal(fonts[0].weight, 700); }); it('should return an empty array for nonexistent family', function() { var fonts = fontManager.findFontsSync({ family: '' + Date.now() }); assert(Array.isArray(fonts)); assert.equal(fonts.length, 0); }); it('should return an empty array for nonexistent postscriptName', function() { var fonts = fontManager.findFontsSync({ postscriptName: '' + Date.now() }); assert(Array.isArray(fonts)); assert.equal(fonts.length, 0); }); it('should return many fonts for empty font descriptor', function() { var fonts = fontManager.findFontsSync({}); assert(Array.isArray(fonts)); assert(fonts.length > 0); fonts.forEach(assertFontDescriptor); }); }); describe('findFont', function() { it('should throw if no font descriptor is provided', function() { assert.throws(function() { fontManager.findFont(function(fonts) {}); }, /Expected a font descriptor/); }); it('should throw if font descriptor is not an object', function() { assert.throws(function() { fontManager.findFont(2, function(fonts) {}); }, /Expected a font descriptor/); }); it('should throw if no callback is provided', function() { assert.throws(function() { fontManager.findFont({ family: standardFont }); }, /Expected a callback/); }); it('should throw if callback is not a function', function() { assert.throws(function() { fontManager.findFont({ family: standardFont }, 2); }, /Expected a callback/); }); it('should findFont asynchronously', function(done) { var async = false; fontManager.findFont({ family: standardFont }, function(font) { assert(async); assert.equal(typeof font, 'object'); assert(!Array.isArray(font)); assertFontDescriptor(font); assert.equal(font.family, standardFont); done(); }); async = true; }); it('should find font by postscriptName', function(done) { fontManager.findFont({ postscriptName: postscriptName }, function(font) { assertFontDescriptor(font); assert.equal(font.postscriptName, postscriptName); assert.equal(font.family, standardFont); done(); }); }); it('should find font by family and style', function(done) { fontManager.findFont({ family: standardFont, style: 'Bold' }, function(font) { assertFontDescriptor(font); assert.equal(font.family, standardFont); assert.equal(font.style, 'Bold'); assert.equal(font.weight, 700); done(); }); }); it('should find font by weight', function(done) { fontManager.findFont({ family: standardFont, weight: 700 }, function(font) { assertFontDescriptor(font); assert.equal(font.weight, 700); done(); }); }); it('should find italic font', function(done) { fontManager.findFont({ family: standardFont, italic: true }, function(font) { assertFontDescriptor(font); assert.equal(font.italic, true); done(); }); }); it('should find bold italic font', function(done) { fontManager.findFont({ family: standardFont, italic: true, weight: 700 }, function(font) { assertFontDescriptor(font); assert.equal(font.italic, true); assert.equal(font.weight, 700); done(); }); }); it('should return a fallback font for nonexistent family', function(done) { fontManager.findFont({ family: '' + Date.now() }, function(font) { assertFontDescriptor(font); done(); }); }); it('should return a fallback font for nonexistent postscriptName', function(done) { fontManager.findFont({ postscriptName: '' + Date.now() }, function(font) { assertFontDescriptor(font); done(); }); }); it('should return a fallback font matching traits as best as possible', function(done) { fontManager.findFont({ family: '' + Date.now(), weight: 700 }, function(font) { assertFontDescriptor(font); assert.equal(font.weight, 700); done(); }); }); it('should return a font for empty font descriptor', function(done) { fontManager.findFont({}, function(font) { assertFontDescriptor(font); done(); }); }); }); describe('findFontSync', function() { it('should throw if no font descriptor is provided', function() { assert.throws(function() { fontManager.findFontSync(); }, /Expected a font descriptor/); }); it('should throw if font descriptor is not an object', function() { assert.throws(function() { fontManager.findFontSync(2); }, /Expected a font descriptor/); }); it('should findFonts synchronously', function() { var font = fontManager.findFontSync({ family: standardFont }); assert.equal(typeof font, 'object'); assert(!Array.isArray(font)); assertFontDescriptor(font); }); it('should find font by postscriptName', function() { var font = fontManager.findFontSync({ postscriptName: postscriptName }); assertFontDescriptor(font); assert.equal(font.postscriptName, postscriptName); assert.equal(font.family, standardFont); }); it('should find font by family and style', function() { var font = fontManager.findFontSync({ family: standardFont, style: 'Bold' }); assertFontDescriptor(font); assert.equal(font.family, standardFont); assert.equal(font.style, 'Bold'); assert.equal(font.weight, 700); }); it('should find font by weight', function() { var font = fontManager.findFontSync({ family: standardFont, weight: 700 }); assertFontDescriptor(font); assert.equal(font.weight, 700); }); it('should find italic font', function() { var font = fontManager.findFontSync({ family: standardFont, italic: true }); assertFontDescriptor(font); assert.equal(font.italic, true); }); it('should find bold italic font', function() { var font = fontManager.findFontSync({ family: standardFont, italic: true, weight: 700 }); assertFontDescriptor(font); assert.equal(font.italic, true); assert.equal(font.weight, 700); }); it('should return a fallback font for nonexistent family', function() { var font = fontManager.findFontSync({ family: '' + Date.now() }); assertFontDescriptor(font); }); it('should return a fallback font for nonexistent postscriptName', function() { var font = fontManager.findFontSync({ postscriptName: '' + Date.now() }); assertFontDescriptor(font); }); it('should return a fallback font matching traits as best as possible', function() { var font = fontManager.findFontSync({ family: '' + Date.now(), weight: 700 }); assertFontDescriptor(font); assert.equal(font.weight, 700); }); it('should return a font for empty font descriptor', function() { var font = fontManager.findFontSync({}); assertFontDescriptor(font); }); }); describe('substituteFont', function() { it('should throw if no postscript name is provided', function() { assert.throws(function() { fontManager.substituteFont(function(font) {}); }, /Expected postscript name/); }); it('should throw if postscript name is not a string', function() { assert.throws(function() { fontManager.substituteFont(2, 'hi', function(font) {}); }, /Expected postscript name/); }); it('should throw if no substitution string is provided', function() { assert.throws(function() { fontManager.substituteFont(postscriptName, function(font) {}); }, /Expected substitution string/); }); it('should throw if substitution string is not a string', function() { assert.throws(function() { fontManager.substituteFont(postscriptName, 2, function(font) {}); }, /Expected substitution string/); }); it('should throw if no callback is provided', function() { assert.throws(function() { fontManager.substituteFont(postscriptName, '汉字'); }, /Expected a callback/); }); it('should throw if callback is not a function', function() { assert.throws(function() { fontManager.substituteFont(postscriptName, '汉字', 52); }, /Expected a callback/); }); it('should substituteFont asynchronously', function(done) { var async = false; fontManager.substituteFont(postscriptName, '汉字', function(font) { assert(async); assert.equal(typeof font, 'object'); assert(!Array.isArray(font)); assertFontDescriptor(font); assert.notEqual(font.postscriptName, postscriptName); done(); }); async = true; }); it('should return the same font if it already contains the requested characters', function(done) { fontManager.substituteFont(postscriptName, 'hi', function(font) { assertFontDescriptor(font); assert.equal(font.postscriptName, postscriptName); done(); }); }); it('should return a default font if no font exists for the given postscriptName', function(done) { fontManager.substituteFont('' + Date.now(), '汉字', function(font) { assertFontDescriptor(font); done(); }); }); }); describe('substituteFontSync', function() { it('should throw if no postscript name is provided', function() { assert.throws(function() { fontManager.substituteFontSync(); }, /Expected postscript name/); }); it('should throw if postscript name is not a string', function() { assert.throws(function() { fontManager.substituteFontSync(2, 'hi'); }, /Expected postscript name/); }); it('should throw if no substitution string is provided', function() { assert.throws(function() { fontManager.substituteFontSync(postscriptName); }, /Expected substitution string/); }); it('should throw if substitution string is not a string', function() { assert.throws(function() { fontManager.substituteFontSync(postscriptName, 2); }, /Expected substitution string/); }); it('should substituteFont synchronously', function() { var font = fontManager.substituteFontSync(postscriptName, '汉字'); assert.equal(typeof font, 'object'); assert(!Array.isArray(font)); assertFontDescriptor(font); assert.notEqual(font.postscriptName, postscriptName); }); it('should return the same font if it already contains the requested characters', function() { var font = fontManager.substituteFontSync(postscriptName, 'hi'); assertFontDescriptor(font); assert.equal(font.postscriptName, postscriptName); }); it('should return a default font if no font exists for the given postscriptName', function() { var font = fontManager.substituteFontSync('' + Date.now(), '汉字'); assertFontDescriptor(font); }); }); }); ================================================ FILE: test/mocha.opts ================================================ --reporter spec ================================================ FILE: travis-linux.sh ================================================ #!/bin/bash # make sure we install the new version of libfontconfig1-dev sudo apt-add-repository 'deb http://archive.ubuntu.com/ubuntu trusty main restricted universe multiverse' sudo apt-get -yqq update sudo apt-get install -y -t trusty libfontconfig1-dev # install some fonts needed for the tests sudo apt-get install -y fonts-droid fonts-liberation