From dbfe28094cf93f722fc9805d3e8e3b6eb49f6c25 Mon Sep 17 00:00:00 2001 From: Jermolene Date: Sat, 10 Mar 2018 10:33:34 +0000 Subject: [PATCH] DynaView plugin: Add font "optisizer" A mechanism to choose the optimum font size of a passage of text to yield a particular numbr of characters per line. @BurningTreeC I've made some minor consistency tweaks and cleanups to the viewport stuff, too. --- plugins/tiddlywiki/dynaview/config.multids | 5 + plugins/tiddlywiki/dynaview/config.tid | 5 +- plugins/tiddlywiki/dynaview/docs.tid | 28 +++- plugins/tiddlywiki/dynaview/dynaview.js | 120 ++++++++++++++---- .../dynaview/examples/font-optisizer.tid | 17 +++ .../dynaview/optisizer-maquette.tid | 24 ++++ plugins/tiddlywiki/dynaview/readme.tid | 6 + 7 files changed, 177 insertions(+), 28 deletions(-) create mode 100644 plugins/tiddlywiki/dynaview/config.multids create mode 100644 plugins/tiddlywiki/dynaview/examples/font-optisizer.tid create mode 100644 plugins/tiddlywiki/dynaview/optisizer-maquette.tid diff --git a/plugins/tiddlywiki/dynaview/config.multids b/plugins/tiddlywiki/dynaview/config.multids new file mode 100644 index 000000000..8fcb09ef6 --- /dev/null +++ b/plugins/tiddlywiki/dynaview/config.multids @@ -0,0 +1,5 @@ +title: $:/config/DynaView/ + +Optisizer: no +Optisizer/Text: ABCDEFGHIJKLMnopqrstuvwxyzABCDEFGHIJKLMnopqrstuvwxyz +ViewportDimensions: no diff --git a/plugins/tiddlywiki/dynaview/config.tid b/plugins/tiddlywiki/dynaview/config.tid index aeacf5b8d..64fe4cea7 100644 --- a/plugins/tiddlywiki/dynaview/config.tid +++ b/plugins/tiddlywiki/dynaview/config.tid @@ -1,4 +1,5 @@ title: $:/plugins/tiddlywiki/dynaview/config -<$checkbox tiddler="$:/config/ViewportDimensions" field="text" checked="yes" unchecked="">  enable dynamic saving of the viewport dimensions -- //the values get stored in // $:/state/viewport/width and $:/state/viewport/height +<$checkbox tiddler="$:/config/DynaView/ViewportDimensions" field="text" checked="yes" unchecked=""> Enable dynamic saving of the viewport [[width|$:/state/DynaView/ViewportDimensions/Width]] and [[height|$:/state/DynaView/ViewportDimensions/Height]] + + diff --git a/plugins/tiddlywiki/dynaview/docs.tid b/plugins/tiddlywiki/dynaview/docs.tid index 3a1bfbcac..5a8870ca9 100644 --- a/plugins/tiddlywiki/dynaview/docs.tid +++ b/plugins/tiddlywiki/dynaview/docs.tid @@ -7,7 +7,7 @@ The components of this plugin include: * A background task that: ** performs specified actions when elements are scrolled into view ** updates certain base classes on the `document.body` according to the current zoom level -** if enabled in the dynaview config panel - dynamically stores the viewport dimensions in $:/state/viewport/width and $:/state/viewport/height +** if enabled in the DynaView config panel - dynamically stores the viewport dimensions in $:/state/viewport/width and $:/state/viewport/height * Pre-configured CSS classes to simplify using those base classes * Usage examples @@ -15,6 +15,32 @@ The components of this plugin include: The background task detects when elements with the class `tc-dynaview-set-tiddler-when-visible` scroll into view. The first time that they do, the background task assigns the value in the attribute `data-dynaview-set-value` to the tiddler whose title is in the attribute `data-dynaview-set-tiddler`. This assignment can be tied to a reveal widget to cause content to be displayed when it becomes visible. If the class `tc-dynaview-expand-viewport` is set then the viewport is expanded so that the processing occurs when elements move near the viewport. +! Viewport Size Features + +!! Viewport Size Tracking + +The background task can optionally dynamically update a pair of state tiddlers with the dimensions of the browser viewport. + +* Set the configuration tiddler $:/config/DynaView/ViewportDimensions to the text "yes" to enable this feature +* The viewport dimensions can be found in $:/state/DynaView/ViewportDimensions/Width and $:/state/DynaView/ViewportDimensions/Height + +!! Font "Optisizer" + +The background task can optionally dynamically optimise the font size of a passage of text to match a desired line length. + +* Set the configuration tiddler $:/config/DynaView/Optisizer to the text "yes" to enable this feature +* Optionally, update the configuration tiddler $:/config/DynaView/Optisizer/Text with the "maquette" -- a character string matchng the desired length (this string should not include spaces). +* Assign the following CSS classes to appropriate elements on the page: +** `.tc-dynaview-optisizer-site` for an HTML element whose `offsetWidth` property gives the desired output width +** `.tc-dynaview-optisizer-maquette` for an HTML element that will contain the maquette +* The computed optimum font size can be found in the tiddler $:/state/DynaView/Optisizer/FontSize + +The tiddler $:/plugins/tiddlywiki/dynaview/optisizer-maquette contains an example configuration that can be used to adjust the size of tiddler body text. To use it: + +* Set $:/config/DynaView/Optisizer to the text "yes" +* Set $:/themes/tiddlywiki/vanilla/metrics/bodyfontsize to `{{$:/state/DynaView/Optisizer/FontSize}}` +* Set $:/themes/tiddlywiki/vanilla/metrics/bodylineheight to `1.5` to ensure that the line height matches the font size + ! Zoom Features !! Document Body Zoom Classes diff --git a/plugins/tiddlywiki/dynaview/dynaview.js b/plugins/tiddlywiki/dynaview/dynaview.js index 8e6226a98..f6338191a 100644 --- a/plugins/tiddlywiki/dynaview/dynaview.js +++ b/plugins/tiddlywiki/dynaview/dynaview.js @@ -18,32 +18,100 @@ exports.platforms = ["browser"]; exports.after = ["render"]; exports.synchronous = true; -var isWaitingForAnimationFrame = false; +var isWaitingForAnimationFrame = 0, // Bitmask: + ANIM_FRAME_CAUSED_BY_LOAD = 1, // Animation frame was requested because of page load + ANIM_FRAME_CAUSED_BY_SCROLL = 2, // Animation frame was requested because of page scroll + ANIM_FRAME_CAUSED_BY_RESIZE = 4; // Animation frame was requested because of window resize exports.startup = function() { - window.addEventListener("load",onScrollOrResize,false); - window.addEventListener("scroll",onScrollOrResize,false); - window.addEventListener("resize",onScrollOrResize,false); + window.addEventListener("load",onLoad,false); + window.addEventListener("scroll",onScroll,false); + window.addEventListener("resize",onResize,false); $tw.hooks.addHook("th-page-refreshed",function() { + optisizeFonts(); checkVisibility(); - if($tw.wiki.getTiddlerText("$:/config/ViewportDimensions") === "yes") { - saveViewportDimensions(); - } + saveViewportDimensions(); }); }; -function onScrollOrResize(event) { +function onLoad(event) { if(!isWaitingForAnimationFrame) { - window.requestAnimationFrame(function() { - setZoomClasses(); - checkVisibility(); - if($tw.wiki.getTiddlerText("$:/config/ViewportDimensions") === "yes") { - saveViewportDimensions(); - } - isWaitingForAnimationFrame = false; - }); + window.requestAnimationFrame(worker); + } + isWaitingForAnimationFrame |= ANIM_FRAME_CAUSED_BY_LOAD; +} + +function onScroll(event) { + if(!isWaitingForAnimationFrame) { + window.requestAnimationFrame(worker); + } + isWaitingForAnimationFrame |= ANIM_FRAME_CAUSED_BY_SCROLL; +} + +function onResize(event) { + if(!isWaitingForAnimationFrame) { + window.requestAnimationFrame(worker); + } + isWaitingForAnimationFrame |= ANIM_FRAME_CAUSED_BY_RESIZE; +} + +function worker() { + if(isWaitingForAnimationFrame & (ANIM_FRAME_CAUSED_BY_RESIZE | ANIM_FRAME_CAUSED_BY_LOAD)) { + optisizeFonts(); + saveViewportDimensions(); + } + setZoomClasses(); + checkVisibility(); + isWaitingForAnimationFrame = 0; +} + +var lastSiteWidth, lastMaquetteString; + +function optisizeFonts() { + if($tw.wiki.getTiddlerText("$:/config/DynaView/Optisizer") === "yes") { + var domSite = document.querySelector(".tc-dynaview-optisizer-site"), + domMaquette = document.querySelector(".tc-dynaview-optisizer-maquette"); + if(domSite && domMaquette) { + // Check that we're not at the same size as last time + if(domSite.offsetWidth === lastSiteWidth && $tw.wiki.getTiddlerText("$:/config/DynaView/Optisizer/Text") === lastMaquetteString) { + return; + } + // Get the current font size + domMaquette.style.fontSize = ""; + var initialFontSize = parseInt(window.getComputedStyle(domMaquette).fontSize,10), + minFontSize = 1, + maxFontSize = 100, + adjustFontSize = maxFontSize, + newFontSize = initialFontSize, + maquetteWidth; + lastSiteWidth = domSite.offsetWidth; + lastMaquetteString = $tw.wiki.getTiddlerText("$:/config/DynaView/Optisizer/Text"); + while(domMaquette.firstChild) { + domMaquette.removeChild(domMaquette.firstChild); + } + domMaquette.appendChild(document.createTextNode(lastMaquetteString)); + // We use a binary search algorithm to find the optimum size + do { + // Apply the size we're considering + domMaquette.style.fontSize = newFontSize + "px"; + // Measure the width of the maquette + maquetteWidth = domMaquette.offsetWidth; + // Adjust bigger or smaller + if(maquetteWidth < lastSiteWidth) { + newFontSize += adjustFontSize; + } else { + newFontSize -= adjustFontSize; + } + newFontSize = Math.min(newFontSize,maxFontSize); + newFontSize = Math.max(newFontSize,minFontSize); + adjustFontSize = adjustFontSize / 2; + } while (adjustFontSize > 0.5); + var newFontSizeString = newFontSize + "px"; + if($tw.wiki.getTiddlerText("$:/state/DynaView/Optisizer/FontSize") !== newFontSizeString) { + $tw.wiki.setText("$:/state/DynaView/Optisizer/FontSize",undefined,undefined,newFontSizeString,undefined); + } + } } - isWaitingForAnimationFrame = true; } function setZoomClasses() { @@ -91,20 +159,22 @@ function checkVisibility() { var tiddler = element.getAttribute("data-dynaview-set-tiddler"), value = element.getAttribute("data-dynaview-set-value") || ""; if(tiddler && $tw.wiki.getTiddlerText(tiddler) !== value) { - $tw.wiki.addTiddler(new $tw.Tiddler({title: tiddler, text: value})); + $tw.wiki.addTiddler(new $tw.Tiddler({title: tiddler, text: value})); } } }); } function saveViewportDimensions() { - var viewportWidth = window.innerWidth || document.documentElement.clientWidth, - viewportHeight = window.innerHeight || document.documentElement.clientHeight; - if($tw.wiki.getTiddlerText("$:/state/viewport/width") !== viewportWidth.toString()) { - $tw.wiki.setText("$:/state/viewport/width",undefined,undefined,viewportWidth.toString(),undefined); - } - if($tw.wiki.getTiddlerText("$:/state/viewport/height") !== viewportHeight.toString()) { - $tw.wiki.setText("$:/state/viewport/height",undefined,undefined,viewportHeight.toString(),undefined); + if($tw.wiki.getTiddlerText("$:/config/DynaView/ViewportDimensions") === "yes") { + var viewportWidth = window.innerWidth || document.documentElement.clientWidth, + viewportHeight = window.innerHeight || document.documentElement.clientHeight; + if($tw.wiki.getTiddlerText("$:/state/DynaView/ViewportDimensions/Width") !== viewportWidth.toString()) { + $tw.wiki.setText("$:/state/DynaView/ViewportDimensions/Width",undefined,undefined,viewportWidth.toString(),undefined); + } + if($tw.wiki.getTiddlerText("$:/state/DynaView/ViewportDimensions/Height") !== viewportHeight.toString()) { + $tw.wiki.setText("$:/state/DynaView/ViewportDimensions/Height",undefined,undefined,viewportHeight.toString(),undefined); + } } } diff --git a/plugins/tiddlywiki/dynaview/examples/font-optisizer.tid b/plugins/tiddlywiki/dynaview/examples/font-optisizer.tid new file mode 100644 index 000000000..ff11db6aa --- /dev/null +++ b/plugins/tiddlywiki/dynaview/examples/font-optisizer.tid @@ -0,0 +1,17 @@ +title: $:/plugins/tiddlywiki/dynaview/examples/font-optisizer +tags: $:/tags/dynaviewExamples +caption: Font Optisizer + +<$button> +<$action-setfield $tiddler="$:/config/DynaView/Optisizer" $value="yes"/> +<$action-setfield $tiddler="$:/themes/tiddlywiki/vanilla/metrics/bodyfontsize" $value="{{$:/state/DynaView/Optisizer/FontSize}}"/> +<$action-setfield $tiddler="$:/themes/tiddlywiki/vanilla/metrics/bodylineheight" $value="1.5"/> +Enable font optisizer for tiddler body text + + +<$button> +<$action-setfield $tiddler="$:/config/DynaView/Optisizer" $value="no"/> +<$action-setfield $tiddler="$:/themes/tiddlywiki/vanilla/metrics/bodyfontsize" $value="14px"/> +<$action-setfield $tiddler="$:/themes/tiddlywiki/vanilla/metrics/bodylineheight" $value="20px"/> +Disable font optisizer + diff --git a/plugins/tiddlywiki/dynaview/optisizer-maquette.tid b/plugins/tiddlywiki/dynaview/optisizer-maquette.tid new file mode 100644 index 000000000..127259e61 --- /dev/null +++ b/plugins/tiddlywiki/dynaview/optisizer-maquette.tid @@ -0,0 +1,24 @@ +title: $:/plugins/tiddlywiki/dynaview/optisizer-maquette +tags: $:/tags/AboveStory + +
+ + + +
+ +
+ +
+ +
+ +
+ +
+ +
diff --git a/plugins/tiddlywiki/dynaview/readme.tid b/plugins/tiddlywiki/dynaview/readme.tid index 846d65e27..95973701e 100644 --- a/plugins/tiddlywiki/dynaview/readme.tid +++ b/plugins/tiddlywiki/dynaview/readme.tid @@ -6,11 +6,17 @@ This plugin makes it possible to build user interfaces that dynamically respond * CSS classes that allow rendering to be deferred until the output is scrolled into view * CSS classes that allow the opacity of DOM elements to vary according to the current zoom level +* A daemon that can dynamically update a pair of state tiddlers with the current dimensions of the browser viewport +* A daemon that can dynamically adjust the size of text to yield a particular number of characters per line Some points to note about the zoom features: +<<< + * The zoom level currently only works on Safari, both on Mac OS and on the iPhone/iPad * The zoom level tracked by the plugin is the pinch-zoom level, and not the text-zoom level * Rather than being progressively rendered as needed, hidden item are rendered with zero opacity. Which means that they can still be interacted with This is really just a proof of concept to allow the user experience to be evaluated. A production version would need to work in all browsers, which would mean adopting a polyfill such as [[Hammer.js|http://hammerjs.github.io/]] to give us manual pan and zoom support. It would also allow deeper levels of zoom. + +<<<