├── .github └── FUNDING.yml ├── LICENSE ├── README.md ├── fit-to-width.js └── test ├── test-bahnschrift.html └── test-fit.html /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: lorp 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | custom: https://www.axis-praxis.org/donate 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fit-to-width.js 2 | 3 | fit-to-width.js is a tiny JavaScript library for fitting text into text containers in a typographically sensitive way, using standard CSS. Its user-facing function, ftw_fit(), takes a set of elements, and automatically adjusts a variable font’s width (`wdth`) axis, as well as adjustments of letter-spacing and word-spacing, attempting to fill each element with the text that it contains. By default, it adjusts the width axis via CSS `font-variation-settings`, then resorts to a horizontal scale using CSS `transform`. In general, a sequence of operations can be specified, each performed to the best of its ability before proceeding to the next operation if an optimal result has not yet been reached. 4 | 5 | For efficiency it uses a binary search algorithm to converge quickly on a good result. Typically it performs only 8 or so tests to arrive at an ideal solution for the width axis setting, even though there are thousands of possible values. 6 | 7 | There is one user-facing function, **ftw_fit()**. 8 | 9 | ## Quick start 10 | 11 | To try out the library: 12 | 13 | * Create a page with `
` elements containing text you want to fit to width. 14 | 15 | * Set these `
` elements in a variable font with a Width axis. 16 | 17 | * Style these `
` elements to have a specific width, e.g. `width: 600px`. 18 | 19 | * Include the `fit-to-width.js` script at the top of your web page like this: 20 | 21 | > `` 22 | 23 | * Assign the class “**ftw**” to the HTML elements you want to process. 24 | 25 | * When your page and fonts are loaded, call `ftw_fit(".ftw")`. 26 | 27 | * or try this [CodePen](https://codepen.io/lorp/pen/rKMvZP) 28 | 29 | ## Background 30 | 31 | In the table, the first column shows various possible typographic adjustments we can try in order to adjust width. The CSS column shows how we adjust this with CSS. The third column shows the method name we use in fit-to-width.js. The fourth column shows whether or not this adjustment is implemented in fit-to-width.js. 32 | 33 | | Adjustment | CSS | Method | Implemented | 34 | |------------------|------------------------|--|-----| 35 | | tracking | `letter-spacing` | **letter-spacing** | ✓ | 36 | | font width | `font-stretch` | **font-stretch** | ✓ | 37 | | font width | `font-variation-settings: 'wdth' ` | **font-variation-settings:wdth** | ✓ | 38 | | font size | `font-size` | | | 39 | | word spacing | `word-spacing` | **word-spacing** | ✓ | 40 | | enable ligatures | `font-feature-settings: 'dlig' <0/1>, 'liga' <0/1>` | **ligatures** | ~ | 41 | | horizontal scale | `transform:scale(,1)`) | **transform** | ✓ | 42 | 43 | By default ftw_fit() uses the following methods in this order: 44 | * `font-variation-settings:wdth` 45 | * `transform` 46 | 47 | Note: Chrome has not implemented CSS `font-stretch` for variable fonts, so by default we use the `font-variation-settings:wdth` method instead. This low-level property does not inherit other axis settings, and so other axes revert to default if you are not careful. See below for how to use the `axes` property to set other axes. 48 | 49 | This method was first presented in [Resize textbox with variable fonts (aka Fit-to-Width)](https://www.axis-praxis.org/blog/2016-11-24/10/demo-resize-textbox-with-variable-fonts-aka-fit-to-width) on the Axis-Praxis blog, November 2016. 50 | 51 | ## Parameters 52 | 53 | `ftw_fit (, [], [] ])` 54 | 55 | ### _elements_ 56 | 57 | The required first parameter, **elements**, specifies the DOM elements we should process. We can specify these elements in several ways: 58 | 59 | * A string, e.g. “.ftw”, is used as a selector to obtain all elements matching `document.querySelectorAll()`. The example will get all elements of class “ftw”. This is similar to element selection in jQuery. 60 | 61 | * A single element, e.g. the return value from `document.getElementById("myId")`. 62 | 63 | * Multiple elements, e.g. the return value from `document.getElementsByClassName("myClass")`. Return values from jQuery’s `$("...")` selection mechanism will also work. 64 | 65 | ### _operations_ 66 | 67 | The optional `operations` parameter is used to customize the sequence of operations of ftw_fit(). 68 | 69 | Default is `["font-variation-settings:wdth", "transform"]` 70 | 71 | Each operation has a method name, which is one of: 72 | 73 | * `font-stretch` 74 | * `font-variation-settings:wdth` 75 | * `word-spacing` 76 | * `letter-spacing` 77 | * `transform` 78 | * `ligatures` 79 | 80 | Each of these can be specified simply, just using a string. To use font-stretch, then letter-spacing, the `operations` parameter is `["font-stretch", "letter-spacing"]`. Each method can also be specified with various other parameters: min, max, maxDiff, maxIterations, axes. 81 | 82 | #### `font-stretch` [binary search] 83 | This ftw_fit() method ideally would use CSS `font-stretch`, which is specified as the standard method of adjusting width in variable fonts. Browsers implementing the property inherit just the `wdth` axis setting, so weight applied using other CSS will also be respected. 84 | 85 | There are two significant disadvantages in using CSS font-stretch now, however: 86 | 87 | 1. Although CSS `font-stretch` is working in Safari it is not supported in Chrome (2018-06), so cross-platform code must use CSS `font-variation-settings`. The `font-variation-settings:wdth` method is implemented in ftw_fit() for this purpose. 88 | 89 | 2. CSS font-stretch uses % units, where 100% is normal width, 50% is a notional half-width, and 200% is a notional double width font. According to the [OpenType specification](https://docs.microsoft.com/en-us/typography/opentype/spec/dvaraxistag_wdth), these values are supposed to come directly from `wdth` axis coordinates. Unfortunately, many existing variable fonts use `wdth` axis values which do not make sense as percentages; the range 0 to 1000 is common, and negative values are also seen. Such non-compliant values are not handled well by browsers. Default min and max for ftw_fit() are 0.00001 and 32767.99998. Note that values of `font-stretch` <= 0 are invalid. 90 | 91 | #### `font-variation-settings:wdth` [binary search] 92 | 93 | This ftw_fit() method uses the low-level CSS `font-variation-settings` property. It works on all browser platforms where variable fonts are supported. Care must be taken because it overrides axis settings made elsewhere, for example an initial or inherited `wght` axis setting. See the `axes` method property for a way to specify other axes. Default min and max are -32768 and 32767.99998 (the extremes of the Fixed 16.16 representation). 94 | 95 | #### `word-spacing` [binary search] 96 | 97 | This ftw_fit() method uses CSS `word-spacing` to make the text fit the container. Default min and max are -0.2 and 20. 98 | 99 | #### `letter-spacing` [binary search] 100 | 101 | This ftw_fit() method uses CSS `letter-spacing` to make the text fit the container. Default min and max are -0.05 and 1. 102 | 103 | #### `transform` 104 | 105 | This ftw_fit() method uses CSS `transform:scale(n,1)`, where `n` is the scale by which the element must be stretched (>1) or squashed (<1) to fit the container. There are no other properties. 106 | 107 | #### `ligatures` 108 | 109 | Not ready for use. This method always applies `font-variation-settings:'dlig' 1,'liga' 1`. The idea is that we should turn on ligatures if we hope for a narrower setting. 110 | 111 | #### Method properties 112 | * `min` is the minimum value to be used in the binary search. If you don’t want `wdth` axis values below a certain value, specify it here. If you know the minimum width axis value, specify it here to save a few iterations. 113 | * `max` is the maximum value to be used in the binary search. If you don’t want `wdth` axis values above a certain value, specify it here. If you know the maximum width axis value, specify it here to save a few iterations. 114 | * `maxDiff` is the largest difference, measured in px units, from the targetWidth that we accept before proceeding to the next operation. Default is 1. 115 | * `maxIterations` is the number of tests we perform before giving up. Default is 50. 116 | * `axes` (used only in the `font-variation-settings:wdth` method) specifies axis locations other than `wdth`. These get appended to the `font-variation-settings` CSS. For example, if you want to run ftw_fit() while keeping `wght` at 788 and `opsz` at 36, then specify `axes:"'wght' 788, 'opsz' 36"`. You might use `getComputedStyle()` to find current weight, in order to use an inherited or initial weight of the element. 117 | 118 | 119 | #### Examples of values for the operations parameter 120 | 121 | * `["font-variation-settings:wdth", "transform"]` (default, so you can omit it if you want this) 122 | * `[{method:"font-stretch",min: 0.61998, max: 1.3}]` (setting min and max to the min and max of the font’s weight axis helps it converge more quickly) 123 | * `[{method:"font-variation-settings:wdth", axes:"'wght' 280, 'opsz' 22"}]` (keep `wght` axis at 280 and `opsz` axis at 22 while adjusting the `wdth` axis) 124 | 125 | ### _targetWidth_ 126 | 127 | The optional `targetWidth` parameter is used to set a pixel width for the element. Default is to use the current width of the elements. 128 | 129 | **Be careful not to apply `ftw_fit()` on elements of `auto` width.** 130 | 131 | ## Return value 132 | 133 | Returns an object which has properties: 134 | * `elapsedTime`: the total time in ms 135 | * `operations`: the operations used to adjust elements 136 | 137 | ## Simple example 138 | 139 | ```html 140 | 141 | 148 | 149 |
150 | Here is 151 |
152 |
153 | Here is more 154 |
155 |
156 | Here is yet more text 157 |
158 | 159 | 162 | 163 | ``` 164 | 165 | 166 | ## Advanced example 167 | 168 | ```html 169 | 174 | 175 |
176 | Here is a more advanced example 177 |
178 | 179 | 182 | ``` 183 | This will first attempt to fit using font-variation-settings, then try letter-spacing, then finally (if those still have not fit the text) transform. 184 | 185 | ## Performance 186 | 187 | Performance is good since it uses binary search on `font-stretch` and `letter-spacing`. Iterations are limited to 50, in case the algorithm fails to converge. The alrogithm typically converges in 7 to 9 iterations. 188 | 189 | ## Issues 190 | 191 | * Sometimes the font is not ready in time for a container’s `clientWidth` to be measured, even when document.fonts.ready has resolved to true. Reloading the page or adding a 2 ms timeout seems to solve the problem, but a solution is needed. This appears to be a bug since [the CSS spec states](https://drafts.csswg.org/css-font-loading-3/#font-face-set-ready) “The ready promise is only fulfilled after layout operations complete” 192 | 193 | * Currently (2018-06) only Safari supports `font-stretch`. You must use the `font-variation-settings:wdth` method for Chrome. 194 | 195 | * Unfortunately, in many browsers letter-spacing is added to glyphs even when a glyph is last on a line. This is typographically incorrect and [the CSS specification is clear](https://drafts.csswg.org/css-text-3/#letter-spacing-property): “Letter-spacing must not be applied at the beginning or at the end of a line”. 196 | 197 | * Glyph sidebearings mean that lines of large font-size or large font-width do not align precisely with lines of small font-size or large font-width. It could be a good idea to add customization for this, but it would probably have to be tuned for each font. 198 | 199 | * On macOS, system variable fonts (Skia and SF) when specified by `font-family`, do not properly clamp axis values to their minimum and maximum. A `wdth` axis setting of -32768 is valid in CSS, but it reverts to default width in Skia. To use system Skia and SF, be sure to specify axis extrema, as in `ftw_fit(".ftw", [{method: "font-variation-settings:wdth", min: 0.61998, max: 1.3}])`. 200 | 201 | ## Similar projects 202 | 203 | * [FitText](http://fittextjs.com), a jQuery plugin by [Paravel](https://paravelinc.com) that adjusts CSS font-size to make text fit a given width. 204 | 205 | * [FitText.js](https://github.com/adactio/FitText.js), a tiny JavaScript library by [Jeremy Keith](https://github.com/adactio) that does the same thing as FitText but without the jQuery dependency. 206 | 207 | * [Fitty](https://www.npmjs.com/package/fitty) by [Bram Stein](https://github.com/bramstein). 208 | 209 | * [typeset](https://github.com/bramstein/typeset) by [Bram Stein](https://github.com/bramstein), the TeX line breaking algorithm in JavaScript. This scales each line horizontally by a different amount to reduce whitespace caused by justification. 210 | 211 | * [Font-To-Width](http://font-to-width.com) is a small JavaScript library by [Nick Sherman](http://nicksherman.com/) and [Chris Lewis](http://chrissam42.com/) that takes advantage of large type families to fit pieces of text snugly within their containers. 212 | 213 | * [Fitting Text to a Container](https://css-tricks.com/fitting-text-to-a-container/) is a list of various text-fitting tricks, compiled by CSS Tricks’ Chris Coyier. 214 | -------------------------------------------------------------------------------- /fit-to-width.js: -------------------------------------------------------------------------------- 1 | // fit-to-width.js 2 | 3 | /* 4 | 5 | Fits text to the width of its DOM container using various methods. 6 | 7 | The core function is ftw_fit(), to which you pass a DOM element or an array of DOM elements. This applies various width adjustment methods, until either succeeding or giving up. Default (which is easily configurable) is to first try CSS font-stretch, then CSS letter-spacing, then finally CSS transform. 8 | 9 | */ 10 | 11 | // set up defaults for each method 12 | const ftw_methods = { 13 | "font-stretch": {min: 0.00001, max: 0x8000 - 1/0x10000, bsFunction: ftw_setFontStretch}, 14 | "font-variation-settings:wdth": {min: -0x8000, max: 0x8000 - 1/0x10000, bsFunction: ftw_setFontVariationSettingsWdth}, 15 | "letter-spacing": {min: -0.05, max: 1, bsFunction: ftw_setLetterSpacing}, 16 | "word-spacing": {min: -0.2, max: 20, bsFunction: ftw_setWordSpacing}, 17 | "transform": {}, 18 | "ligatures": {} 19 | }; 20 | 21 | // function to check if iterable 22 | const ftw_ArgIsIterable = object => object != null && typeof object[Symbol.iterator] === 'function'; 23 | 24 | function ftw_setFontStretch (el, val, operation) { 25 | el.style.fontStretch = val + "%"; 26 | } 27 | 28 | function ftw_setFontVariationSettingsWdth (el, val, operation) { 29 | let fvsString = "'wdth' " + val; 30 | if (operation.axes) 31 | fvsString += "," + operation.axes; 32 | el.style.fontVariationSettings = fvsString; 33 | } 34 | 35 | function ftw_setLetterSpacing (el, val) { 36 | el.style.letterSpacing = val + "em"; 37 | } 38 | 39 | function ftw_setWordSpacing (el, val) { 40 | el.style.wordSpacing = val + "em"; 41 | } 42 | 43 | function ftw_Operation (method, min, max, maxDiff, maxIterations, axes) { 44 | if (ftw_methods[method]) { 45 | this.method = method; 46 | this.min = min === undefined ? ftw_methods[method].min : min; 47 | this.max = min === undefined ? ftw_methods[method].max : max; 48 | this.bsFunction = ftw_methods[method].bsFunction; 49 | this.maxDiff = maxDiff === undefined ? 1 : maxDiff; // allows 0 50 | this.maxIterations = maxIterations === undefined ? 50 : maxIterations; 51 | this.axes = axes; 52 | } 53 | else 54 | this.method = null; 55 | } 56 | 57 | // main function 58 | function ftw_fit (elements, ftwOperations, targetWidth) { 59 | 60 | let startTime = performance.now(); 61 | let config = { 62 | operations: ftwOperations || ["font-variation-settings:wdth", "transform"] 63 | }; 64 | let els; 65 | 66 | // get all elements selected by the string elements 67 | if (typeof elements === "string") 68 | els = document.querySelectorAll(elements); 69 | // is elements already a NodeList or array of elements? if so, fine; otherwise make it an array 70 | else if (ftw_ArgIsIterable(elements)) 71 | els = elements; 72 | else 73 | els = [elements]; // convert to an array 74 | 75 | // user config? 76 | if (!Array.isArray(config.operations)) { 77 | if (!config.operations) 78 | config.operations = ["font-stretch"]; 79 | else if (typeof config.operations === "string" || typeof config.operations === "object") 80 | config.operations = [config.operations]; 81 | } 82 | 83 | // for each element supplied by the user 84 | for (let el of els) 85 | { 86 | let success = false; 87 | config.targetWidth = targetWidth || el.clientWidth; 88 | el.style.whiteSpace = "nowrap"; 89 | el.style.width = "max-content"; 90 | el.style.transform = "none"; 91 | 92 | // for each operation specified by the user 93 | for (let op of config.operations) { 94 | let operation; 95 | if (typeof op === "string") 96 | operation = new ftw_Operation(op); 97 | else 98 | operation = new ftw_Operation(op.method, op.min, op.max, op.maxDiff, op.maxIterations, op.axes); 99 | 100 | switch (operation.method) { 101 | case "transform": ftw_fit_transform (el, config); break; 102 | case "ligatures": ftw_fit_ligatures (el, config); break; 103 | case "font-stretch": 104 | case "font-variation-settings:wdth": 105 | case "letter-spacing": 106 | case "word-spacing": 107 | success = ftw_fit_binary_search (el, operation, config.targetWidth); 108 | break; 109 | // ignore unrecognized methods 110 | } 111 | 112 | if (success) 113 | { 114 | console.log (operation.method); 115 | break; 116 | } 117 | } 118 | 119 | // reset element width 120 | el.style.width = config.targetWidth+"px"; // TODO: revert it to its original getComputedStyle() width, e.g. "10em"? 121 | } 122 | 123 | config.elapsedTime = performance.now() - startTime; 124 | return config; 125 | } 126 | 127 | function ftw_fit_binary_search (el, operation, targetWidth) { 128 | 129 | let iterations = 0; 130 | let min = operation.min, max = operation.max; 131 | let minClientWidth, maxClientWidth; 132 | let done = false; 133 | let success = false; 134 | 135 | // checks before binary search 136 | if (min > max) 137 | done = true; 138 | else { 139 | operation.bsFunction(el, min, operation); // above the min? 140 | if ((minClientWidth=el.clientWidth) >= targetWidth) { 141 | done = true; 142 | if (minClientWidth == targetWidth) 143 | success = true; 144 | } 145 | else { 146 | operation.bsFunction(el, max, operation); // below the max? 147 | if ((maxClientWidth=el.clientWidth) < targetWidth) { 148 | done = true; 149 | if (maxClientWidth == targetWidth) 150 | success = true; 151 | } 152 | else if (minClientWidth >= maxClientWidth) {// check width at min != width at max 153 | done = true; 154 | } 155 | } 156 | } 157 | 158 | // the binary search 159 | while (!done) { 160 | 161 | let val = 0.5 * (min+max); 162 | operation.bsFunction(el, val, operation); // set the CSS 163 | let diff = el.clientWidth - targetWidth; // are we under or over? 164 | if (diff <= 0) { 165 | if (diff > -operation.maxDiff) { // SUCCESS: 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 24 | 25 | 26 | 27 | 28 | 29 | Click the button to fit the text! 30 | 31 |
32 | Short title 33 |
34 |
35 | Moderately shortish 36 |
37 |
38 | Waste not want not! Oder? 39 |
40 |
41 | Rolling stones gathers moss. 42 |
43 |
44 | The early bird catches the worm. 45 |
46 |
47 | The quick brown fox jumps over the lazy dog and lorem ipsum dolor sit. 48 |
49 | 50 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /test/test-fit.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 36 | 37 | 38 | 39 | 40 | 41 | Click the button to fit the text! 42 | 43 |
44 | Short title 45 |
46 |
47 | Moderately shortish 48 |
49 |
50 | Waste not want not! Oder? 51 |
52 |
53 | Rolling stones gathers moss. 54 |
55 |
56 | The early bird catches the worm. 57 |
58 |
59 | The quick brown fox jumps over the lazy dog and lorem ipsum dolor sit. 60 |
61 | 62 |

This document references a font that is licensed to Laurence Penney. Special permission has been granted to crosslink to it for demo purposes only. You must obtain an appropriate license at djr.com/fit before linking to this font or using it in any other context.

63 | 64 | 76 | 77 | 78 | 79 | 80 | --------------------------------------------------------------------------------