Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor to simplify supporting new CSS props; add support for variable character width & letter-spacing #36

Open
wants to merge 9 commits into
base: development
Choose a base branch
from

Conversation

tinymachine
Copy link
Collaborator

Okay, here's a cleaner PR than my last attempt. Again, this PR is pretty far-reaching, so I totally understand if you can't accept it. Just thought you might be interested in some aspects of how I approached these changes.

Major change: apply defaults, calculate and adjust property values programmatically

The biggest update here is in 887d31d, which refactors how CSS properties are handled by the script. Instead of 'manually' handling all calculations (with separate lines of code for applying defaults for each property, and calculating and applying each property value), all supported properties and their defaults are defined in a config var. Then defaults are applied for all properties included in the config, and all calculations are made and style changes are applied programmatically.

This way when you want to support additional CSS properties, you just update the config, without having to worry about making additional per-property updates throughout the script. For example, adding letter-spacing support to the script is now as simple as adding the following to config.supportedProps:

{
  propName: 'letterSpacing',
  unitsDefault: 'em'
}

Adding per-property defaults for minWidth and maxWidth is supported as well. Here's the entire config for all five props supported in the PR:

supportedProps: [
  {
    propName: 'fontSize',
    minWidthDefault: 1.0,
    maxWidthDefault: 1.8,
    unitsDefault: 'em'
  },
  {
    propName: 'fontStretch',
    unitsDefault: '%'
  },
  {
    propName: 'fontWeight'
  },
  {
    propName: 'letterSpacing',
    unitsDefault: 'em'
  },
  {
    propName: 'lineHeight',
    minWidthDefault: 1.33,
    maxWidthDefault: 1.25
  }
]

Limitations

  1. Parameter naming restriction: each supported property must have a propName that matches the JS equivalents for CSS properties. The script assumes the propName is what's used by the API params, so the letter-spacing params would (as currently coded) have to be min/maxWidthLetterSpacing (the script is aware of the initial cap on the property name) and letterSpacingUnits. Params for changing character width would (as currently coded) have to be min/maxWidthFontStretch and fontStretchUnits. This could be changed pretty easily to handle param names that don't necessarily map directly to their respective CSS properties, but it might be a good thing to enforce a standard style on the params and to make explicit exactly what the params control.

  2. No support for special CSS properties: properties like font-variation-settings, which can control multiple properties at once, aren't currently supported. This is probably fine, though, since the VF variation axes users will probably generally want to control (and the ones more variable fonts support) would be more standardized variation axes like weight and sometimes width, which honor their own respective CSS properties (e.g. font-weight and font-stretch).

Other updates

Backward compatibility

This PR is backward-compatible with previous versions with the exception of dropping support for min/maxWidthVariableGrade (and min/maxVariableGrade) in favor of min/maxWidthFontWeight (though supporting it could be added fairly easily).

Questions

  • This PR makes the script much easier to update to add support for new CSS properties, but I'm not sure how the script's runtime performance has been affected. If you're aware of how to test performance/efficiency, I'd love to hear about it. (I stumbled across jsPerf, but haven't yet figured out how to set it up for testing Textblock.)

`font-weight` works just like `font-variation-settings: "wght"`, but unlike the latter it doesn't override the CSS cascade (which happens in Chrome and Safari when the `@font-face` declaration includes a `font-weight` range). (See note on https://developer.mozilla.org/en-US/docs/Web/CSS/font-variation-settings: 'font characteristics set using `font-variation-settings` will always override those set using the corresponding basic font properties, e.g. `font-weight`, no matter where they appear in the cascade.') So, if some text within a Textblock block has a specified weight (e.g. for `<strong>` tags), that won't be overridden by the script like it would previously have been.

- Add bold text to demos to show cascade now honored.
More accurately conveys the actual property / variation axis being manipulated. Also, replace 'grades' with 'weights' in demo.html and README (but leave reference to 'simulating grades').
Move description from bottom of Parameters section to top intro.
Make explicit the relationship between container width and the type-related parameters.
- Use a top-level `config` var to contain all defaults and list all supported type-related properties to make it easier to add support for additional CSS properties.

- Allow for units to be specified per-property (e.g. `fontSizeUnits`, though `units` is still respected and applied only to `fontSize`).

- Apply defaults and calculate all CSS property values programmatically instead of handling each property manually. This makes supporting new CSS properties trivial.

- Refactor `run()` & `calc()` to be more modular:
  -- Simplify `run()` function.
  -- Change `calc()` into `calcCurrentPropValue()` to calculate a single property at time.
  -- Separate width-ratio calculation into its own `calcCurrentWidthRatio()` method.
Also:

- Update demos with var. width (font-stretch) & letter-spacing. Include new font (AmstelvarAlpha), which supports variable width. Also add legacy `units` param to the legacy test.

- Update README.
- Replace date with version number in banner. (Prevents meaningless version control changes from being indicated when script is re-minified but nothing has changed other than the date.)

- Update `grunt-contrib-uglify` to 4.0.0 to enable better 'mangling' (i.e. shortened from longer human-friendly names to single characters).

- Remove demo files from watch task. (No need for the JS file to be minified every time the demo files update.)

- Rebuild minified script.
@theorosendorf
Copy link
Collaborator

@tinymachine — whoa, this is amazing!

I only skimmed but really looks like the way to approach it. I won't be able to take a proper look for a while. I'll request @traviskochel and @jessevdp in case they’re available to pop in and see.

Thanks Mihira!

@tinymachine
Copy link
Collaborator Author

My pleasure, @theorosendorf!

@traviskochel
Copy link
Collaborator

Haven't tested it, but it looks good to me. nice work @tinymachine!

@tinymachine
Copy link
Collaborator Author

Thanks, @traviskochel — you and @theorosendorf both do really beautiful and inspiring work. I'm frankly humbled to be able to contribute to this project.

@theorosendorf
Copy link
Collaborator

@tinymachine — This PR also takes care of #34 since formatting becomes customizable, right? I was able to change min/maxWidth to min/maxWidth_ without breaking the demo. Hope you don't mind, I commited that test... :)

@theorosendorf
Copy link
Collaborator

I ran Chrome’s performance monitor and was able to ramp up the CPU usage to 100%, although to do that I had to frantically resize the viewport like a crazed meth addict.

@theorosendorf
Copy link
Collaborator

Humbled to have you here @tinymachine.

@tinymachine
Copy link
Collaborator Author

@tinymachine — This PR also takes care of #34 since formatting becomes customizable, right? I was able to change min/maxWidth to min/maxWidth_ without breaking the demo. Hope you don't mind, I commited that test... :)

I don't mind at all — please feel free to make changes as you see fit! I hadn't considered this PR to close #34 since it didn't actually change the param formatting, but your commit does the trick.

I ran Chrome’s performance monitor and was able to ramp up the CPU usage to 100%, although to do that I had to frantically resize the viewport like a crazed meth addict.

Ha — yeah, not sure if it's warranted but I wonder if it might make sense to throttle, or maybe throttle and debounce, the script and see if it would bring that CPU usage down. (I set up a test on jsPerf but haven't figured out how to get reliable results. I switched up the ordering of which version of the script to test first, and the first script always performed much better than the others, and with a huge margin of error.)

Humbled to have you here @tinymachine.

🙏

@tinymachine
Copy link
Collaborator Author

@theorosendorf, I just tried some crude benchmarking by changing the run(); line in the onDocReady() block of the script to the following to get it to run 10k times:

    var reps = 10000;
    var startTime = Date.now();
    for (var i = 0; i < reps; i++) {
      run();
    }
    var endTime = Date.now();
    console.log(reps + ' executions took ' + (endTime - startTime) / 1000 + ' seconds');

Somewhat consistently I'm seeing a time of ~3.4s for this PR, and ~0.8s for 0.10.0 when I try multiple runs in Safari and Chrome. So though this PR makes it much easier to make changes to the params, etc., it might result in ~4x slower performance 😬. I'll look into this further but wanted to give a heads-up...

@theorosendorf
Copy link
Collaborator

I really love the idea of this PR. Are there any tools we can use to better understand the performance hit?

@theorosendorf
Copy link
Collaborator

You know, because this would squash a ton of current and future features...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants