Skip to content

Commit

Permalink
Merge pull request #5906 from keymanapp/fix/web/5779-font-scaling-in-osk
Browse files Browse the repository at this point in the history
fix(web): font size was not consistently set
  • Loading branch information
mcdurdin committed Dec 8, 2021
2 parents a09cc69 + 9995c9f commit 5ab9574
Show file tree
Hide file tree
Showing 9 changed files with 75 additions and 92 deletions.
Expand Up @@ -140,6 +140,15 @@ namespace com.keyman.keyboards {
var i, j, k, m, row, rows: LayoutRow[], key: LayoutKey, keys: LayoutKey[];
var chiral: boolean = (kbdBitmask & Codes.modifierBitmasks.IS_CHIRAL) != 0;

if(PVK['F']) {
// The KeymanWeb compiler generates a string of the format `[italic ][bold ] 1em "<font>"`
// We will ignore the bold, italic and font size spec
let legacyFontSpec = /^(?:(?:italic|bold) )* *[0-9.eE-]+(?:[a-z]+) "(.+)"$/.exec(PVK['F']);
if(legacyFontSpec) {
layout.font = legacyFontSpec[1];
}
}

var kmw10Plus = !(typeof keyLabels == 'undefined' || !keyLabels);
if(!kmw10Plus) {
// Save the processed key label information to the keyboard's general data.
Expand Down
9 changes: 4 additions & 5 deletions web/source/dom/domEventHandlers.ts
Expand Up @@ -492,8 +492,8 @@ namespace com.keyman.dom {
}

private static selectTouch(e: TouchEvent): Touch {
/**
* During multi-touch event's, it's possible for one or more touches of said multi-touch
/*
* During multi-touch events, it's possible for one or more touches of said multi-touch
* to be against irrelevant parts of the page. We only want to consider touches against
* valid OutputTargets - against elements of the page that KMW can attach to.
* With touch active... that's a TouchAliasElement.
Expand Down Expand Up @@ -521,7 +521,6 @@ namespace com.keyman.dom {

/**
* Handle receiving focus by simulated input field
*
*/
setFocus: (e?: TouchEvent|MSPointerEvent) => void = function(this: DOMTouchHandlers, e?: TouchEvent|MSPointerEvent): void {
DOMEventHandlers.states.setFocusTimer();
Expand Down Expand Up @@ -682,7 +681,7 @@ namespace com.keyman.dom {
}
}

/**
/*
* This event will trigger before keymanweb.setBlur is triggered. Now that we're allowing independent keyboard settings
* for controls, we have to act here to preserve the outgoing control's keyboard settings.
*
Expand All @@ -696,7 +695,7 @@ namespace com.keyman.dom {
this.keyman.domManager.lastActiveElement = target;
target.showCaret();

/**
/*
* If we 'just activated' the KeymanWeb UI, we need to save the new keyboard change as appropriate.
* If not, we need to activate the control's preferred keyboard.
*/
Expand Down
26 changes: 8 additions & 18 deletions web/source/dom/domManager.ts
Expand Up @@ -462,7 +462,7 @@ namespace com.keyman.dom {
return;
};

/**
/**
* Function isKMWDisabled
* Scope Private
* @param {Element} x An element from the page.
Expand Down Expand Up @@ -663,7 +663,7 @@ namespace com.keyman.dom {
}
}

/**
/**
* Function _DetachFromIframe
* Scope Private
* @param {Element} Pelem IFrame to which KMW will be attached
Expand Down Expand Up @@ -1300,7 +1300,7 @@ namespace com.keyman.dom {
*
* @param {Object|string} e element id or element
* @param {boolean=} setFocus optionally set focus (KMEW-123)
**/
*/
setActiveElement(e: string|HTMLElement, setFocus?: boolean) {
if(typeof e == "string") { // Can't instanceof string, and String is a different type.
e = document.getElementById(e);
Expand Down Expand Up @@ -1423,7 +1423,7 @@ namespace com.keyman.dom {
*
* @param {string|Object} e element or element id
*
**/
*/
moveToElement(e:string|HTMLElement) {
var i;

Expand Down Expand Up @@ -1473,7 +1473,7 @@ namespace com.keyman.dom {
* before applying any keymanweb styles or classes
*
* @return {string}
**/
*/
getBaseFont() {
var util = this.keyman.util;
var ipInput = document.getElementsByTagName<'input'>('input'),
Expand Down Expand Up @@ -1614,21 +1614,11 @@ namespace com.keyman.dom {
keyman.modelManager.init();
this.keyman._MasterDocument = window.document;

/**
/*
* Initialization of touch devices and browser interfaces must be done
* after all resources are loaded, during final stage of initialization
*
*/

// Treat Android devices as phones if either (reported) screen dimension is less than 4"
if(device.OS == 'Android')
{
// Determine actual device characteristics I3363 (Build 301)
// TODO: device.dpi may no longer be needed - if so, get rid of it.
var dpi = device.getDPI(); //TODO: this will not work when called from HEAD!!
device.formFactor=((screen.height < 4.0 * dpi) || (screen.width < 4.0 * dpi)) ? 'phone' : 'tablet';
}

// Set exposed initialization flag member for UI (and other) code to use
this.keyman.setInitialized(1);

Expand Down Expand Up @@ -1785,7 +1775,7 @@ namespace com.keyman.dom {
this.attachmentObserver.observe(observationTarget, observationConfig);
}

/**
/*
* Setup of handlers for dynamic detection of the kmw-disabled class tag that controls enablement.
*/
observationConfig = { subtree: true, attributes: true, attributeOldValue: true, attributeFilter: ['class', 'readonly']};
Expand All @@ -1812,7 +1802,7 @@ namespace com.keyman.dom {

/**
* Initialize the desktop user interface as soon as it is ready
**/
*/
initializeUI() {
if(this.keyman.ui && this.keyman.ui['initialize'] instanceof Function) {
this.keyman.ui['initialize']();
Expand Down
63 changes: 35 additions & 28 deletions web/source/kmwdevice.ts
Expand Up @@ -33,36 +33,36 @@ namespace com.keyman {
/**
* Get device horizontal DPI for touch devices, to set actual size of active regions
* Note that the actual physical DPI may be somewhat different.
*
* @return {number}
*/
*
* @return {number}
*/
getDPI(): number {
var t=document.createElement('DIV') ,s=t.style,dpi=96;
if(document.readyState !== 'complete') {
return dpi;
}

t.id='calculateDPI';
s.position='absolute'; s.display='block';s.visibility='hidden';
s.left='10px'; s.top='10px'; s.width='1in'; s.height='10px';
document.body.appendChild(t);
dpi=(typeof window.devicePixelRatio == 'undefined') ? t.offsetWidth : t.offsetWidth * window.devicePixelRatio;
document.body.removeChild(t);
return dpi;
return dpi;
}

detect() : void {
var IEVersion = Device._GetIEVersion();
var possMacSpoof = false;

if(navigator && navigator.userAgent) {
var agent=navigator.userAgent;

if(agent.indexOf('iPad') >= 0) {
this.OS='iOS';
this.formFactor='tablet';
this.dyPortrait=this.dyLandscape=0;
} else if(agent.indexOf('iPhone') >= 0) {
} else if(agent.indexOf('iPhone') >= 0) {
this.OS='iOS';
this.formFactor='phone';
this.dyPortrait=this.dyLandscape=25;
Expand All @@ -78,15 +78,15 @@ namespace com.keyman {
} else if(agent.indexOf('Linux') >= 0) {
this.OS='Linux';
} else if(agent.indexOf('Macintosh') >= 0) {
// Starting with 13.1, "Macintosh" can reflect iPads (by default) or iPhones
// Starting with 13.1, "Macintosh" can reflect iPads (by default) or iPhones
// (by user setting); a new "Request Desktop Website" setting for Safari will
// change the user agent string to match a desktop Mac.
//
// Firefox uses '.' between version components, while Chrome and Safari use
// '_' instead. So, we have to check for both. Yay.
let regex = /Intel Mac OS X (\d+(?:[_\.]\d+)+)/i;
let results = regex.exec(agent);

// Match result: a version string with components separated by underscores.
if(!results) {
console.warn("KMW could not properly parse the user agent string."
Expand All @@ -105,22 +105,29 @@ namespace com.keyman {
if(agent.indexOf('Touch') >= 0) {
this.formFactor='phone'; // will be redefined as tablet if resolution high enough
}

// Windows Phone and Tablet PC
if(typeof navigator.msMaxTouchPoints == 'number' && navigator.msMaxTouchPoints > 0) {
this.touchable=true;
}
}
}

// var sxx=device.formFactor;
// Check and possibly revise form factor according to actual screen size (adjusted for Galaxy S, maybe OK generally?)
if(this.formFactor == 'tablet' && Math.min(screen.width,screen.height) < 400) {
this.formFactor='phone';
}

// Trust what iOS tells us for phone vs tablet.
if(this.formFactor == 'phone' && Math.max(screen.width,screen.height) > 720 && this.OS != 'iOS') {
// We look at the screen resolution for Android, because we can't tell from
// the user agent string whether or not this is supposed to be a tablet.
// It seems that there are a handful of older phones out there that report a
// higher resolution than 700px*___px, but it is proving hard to test these,
// and the majority have an aspect ratio <= 0.5625 anyway.
// But we trust what iOS tells us for phone vs tablet.

const dimMin = Math.min(screen.width,screen.height), dimMax = Math.max(screen.width,screen.height);
const aspect = dimMin / dimMax;

if(this.OS != 'iOS' &&
this.formFactor == 'phone' &&
((dimMin >= 600 && aspect > 0.5625) || // 0.5625 -> 1920x1080 is common phone res
(aspect >= 0.625)) // all reported devices with aspect >= 0.625 are tablets per https://screensiz.es/
) {
this.formFactor='tablet';
}

Expand All @@ -132,7 +139,7 @@ namespace com.keyman {
if(this.OS == 'iOS' && !('ongesturestart' in window) && !possibleChromeEmulation) {
this.OS='Android';
}

// Determine application or browser
this.browser='web';
if(IEVersion < 999) {
Expand Down Expand Up @@ -160,7 +167,7 @@ namespace com.keyman {
} else if(navigator.userAgent.indexOf('Safari') >= 0) {
this.browser='safari';
}
}
}
}

if(possMacSpoof && this.browser == 'safari') {
Expand Down Expand Up @@ -193,41 +200,41 @@ namespace com.keyman {

static _GetIEVersion() {
var n, agent='';

if('userAgent' in navigator) {
agent=navigator.userAgent;
}

// Test first for old versions
if('selection' in document) { // only defined for IE and not for IE 11!!!
if('selection' in document) { // only defined for IE and not for IE 11!!!
var appVer=navigator.appVersion;
n=appVer.indexOf('MSIE ');
if(n >= 0) {
// Check for quirks mode page, always return 6 if so
if((document as Document).compatMode == 'BackCompat') {
return 6;
}

appVer=appVer.substr(n+5);
n=appVer.indexOf('.');
if(n > 0) {
return parseInt(appVer.substr(0,n),10);
}
}
}
}

// Finally test for IE 11 (and later?)
n=agent.indexOf('Trident/');
if(n < 0) {
return 999;
}

agent=agent.substr(n+8);
n=agent.indexOf('.');
if(n > 0){
return parseInt(agent.substr(0,n),10)+4;
}

return 999;
}

Expand Down
5 changes: 5 additions & 0 deletions web/source/kmwutils.ts
Expand Up @@ -442,6 +442,11 @@ namespace com.keyman {
// This can sometimes fail with some browsers if called before document defined,
// so catch the exception
try {
// For emulation of iOS on a desktop device, use a default value
if(this.device.formFactor == 'desktop') {
return 1;
}

// Get viewport width
var viewportWidth = document.documentElement.clientWidth;

Expand Down
4 changes: 2 additions & 2 deletions web/source/osk/oskBaseKey.ts
Expand Up @@ -54,7 +54,7 @@ namespace com.keyman.osk {
let q = document.createElement('div');
q.className='kmw-key-label';
if(x > 0) {
q.innerHTML=String.fromCharCode(x);
q.innerText=String.fromCharCode(x);
} else {
// Keyman-only virtual keys have no corresponding physical key.
// So, no text for the key-cap.
Expand Down Expand Up @@ -128,7 +128,7 @@ namespace com.keyman.osk {
this.square.style.width = vkbd.layoutWidth.scaledBy(key.proportionalWidth).styleString;
this.square.style.marginLeft = vkbd.layoutWidth.scaledBy(key.proportionalPad).styleString;
this.btn.style.width = vkbd.usesFixedWidthScaling ? this.square.style.width : '100%';

if(vkbd.usesFixedHeightScaling) {
// Matches its row's height.
this.square.style.height = vkbd.layoutHeight.scaledBy(this.row.heightFraction).styleString;
Expand Down
36 changes: 10 additions & 26 deletions web/source/osk/oskView.ts
Expand Up @@ -374,25 +374,9 @@ namespace com.keyman.osk {

public defaultFontSize(device: utils.DeviceSpec, isEmbedded: boolean): ParsedLengthStyle {
if(device.touchable) {
var fontScale: number = 1;
if(device.formFactor == 'phone') {
fontScale = 1.6 * (isEmbedded ? 0.65 : 0.6) * 1.2; // Combines original scaling factor with one previously applied to the layer group.
} else {
// The following is a *temporary* fix for small format tablets, e.g. PendoPad
var pixelRatio = 1;
if(device.OS == 'android' && 'devicePixelRatio' in window) {
pixelRatio = window.devicePixelRatio;
}

let defaultHeight = this.bannerView.height + this.getDefaultKeyboardHeight();
if(device.OS == 'android' && device.formFactor == 'tablet' && defaultHeight < 300 * pixelRatio) {
fontScale *= 1.2;
} else {
fontScale *= 2; //'2.5em';
}
}

// Finalize the font size parameter.
const fontScale = device.formFactor == 'phone'
? 1.6 * (isEmbedded ? 0.65 : 0.6) * 1.2 // Combines original scaling factor with one previously applied to the layer group.
: 2; // iPad or Android tablet
return ParsedLengthStyle.special(fontScale, 'em');
} else {
return this.computedHeight ? ParsedLengthStyle.inPixels(this.computedHeight / 8) : undefined;
Expand Down Expand Up @@ -487,13 +471,11 @@ namespace com.keyman.osk {
this.needsLayout = false;

// Step 3: perform layout operations.
if(!this._baseFontSize && this.parsedBaseFontSize) {
// Make sure to initialize the default font size if it hasn't already been set!
this.banner.element.style.fontSize = this.baseFontSize;
if(this.vkbd) {
this.vkbd.fontSize = this.parsedBaseFontSize;
}
this.banner.element.style.fontSize = this.baseFontSize;
if(this.vkbd) {
this.vkbd.fontSize = this.parsedBaseFontSize;
}

if(!pending) {
this.headerView?.refreshLayout();
this.bannerView.refreshLayout();
Expand Down Expand Up @@ -627,7 +609,9 @@ namespace com.keyman.osk {

// Ensure the keyboard view is modeling the correct state. (Correct layer, etc.)
this.keyboardView.updateState();
this.refreshLayoutIfNeeded();
// We need to recalc the font size here because the layer did not have
// calculated dimensions available before it was visible
this.refreshLayout();
}
}.bind(this);

Expand Down

0 comments on commit 5ab9574

Please sign in to comment.