Skip to content
134 changes: 132 additions & 2 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,8 @@ <h2><code>PointerEvent</code> Interface</h2>
long tiltX = 0;
long tiltY = 0;
long twist = 0;
double altitudeAngle = 0;
double azimuthAngle = 0;
DOMString pointerType = "";
boolean isPrimary = false;
sequence&lt;PointerEvent> coalescedEvents = [];
Expand All @@ -304,6 +306,8 @@ <h2><code>PointerEvent</code> Interface</h2>
readonly attribute long tiltX;
readonly attribute long tiltY;
readonly attribute long twist;
readonly attribute double altitudeAngle;
readonly attribute double azimuthAngle;
readonly attribute DOMString pointerType;
readonly attribute boolean isPrimary;
sequence&lt;PointerEvent> getCoalescedEvents();
Expand Down Expand Up @@ -337,15 +341,15 @@ <h2><code>PointerEvent</code> Interface</h2>
</dd>
<dt><dfn>tiltX</dfn></dt>
<dd>
<p>The plane angle (in degrees, in the range of [-90,90]) between the Y-Z plane and the plane containing both the transducer (e.g. pen stylus) axis and the Y axis. A positive <code>tiltX</code> is to the right. <code>tiltX</code> can be used along with <code>tiltY</code> to represent the tilt away from the normal of a transducer with the digitizer. For hardware and platforms that do not report tilt, the value MUST be 0.</p>
<p>The plane angle (in degrees, in the range of [-90,90]) between the Y-Z plane and the plane containing both the transducer (e.g. pen stylus) axis and the Y axis. A positive <code>tiltX</code> is to the right. <code>tiltX</code> can be used along with <code>tiltY</code> to represent the tilt away from the normal of a transducer with the digitizer. For hardware and platforms that do not report tilt or angle, the value MUST be 0.</p>
<figure id="figure_tiltX">
<img src="tiltX_600px.png" alt="tiltX explanation diagram">
<figcaption>Positive <code>tiltX</code>.</figcaption>
</figure>
</dd>
<dt><dfn>tiltY</dfn></dt>
<dd>
<p>The plane angle (in degrees, in the range of [-90,90]) between the X-Z plane and the plane containing both the transducer (e.g. pen stylus) axis and the X axis. A positive <code>tiltY</code> is towards the user. <code>tiltY</code> can be used along with <code>tiltX</code> to represent the tilt away from the normal of a transducer with the digitizer. For hardware and platforms that do not report tilt, the value MUST be 0.</p>
<p>The plane angle (in degrees, in the range of [-90,90]) between the X-Z plane and the plane containing both the transducer (e.g. pen stylus) axis and the X axis. A positive <code>tiltY</code> is towards the user. <code>tiltY</code> can be used along with <code>tiltX</code> to represent the tilt away from the normal of a transducer with the digitizer. For hardware and platforms that do not report tilt or angle, the value MUST be 0.</p>
<figure id="figure_tiltY">
<img src="tiltY_600px.png" alt="tiltY explanation diagram">
<figcaption>Negative <code>tiltY</code>.</figcaption>
Expand All @@ -355,6 +359,14 @@ <h2><code>PointerEvent</code> Interface</h2>
<dd>
<p>The clockwise rotation (in degrees, in the range of [0,359]) of a transducer (e.g. pen stylus) around its own major axis. For hardware and platforms that do not report twist, the value MUST be 0.</p>
</dd>
<dt><dfn>altitudeAngle</dfn></dt>
<dd>
<p>The altitude (in radians) of the transducer (e.g. pen stylus), in the range [0,π/2] - where 0 is parallel to the surface (X-Y plane), and π/2 is perpendicular to the surface. For hardware and platforms that do not report tilt or angle, the value MUST be 0.</p>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the default value of altitude should match the default value of tiltX/TiltY. So if they are 0 (for example when things aren't supported) then altitude should be pi/2.

One general suggestion is that if you add a new line after every comma or sentence it avoids the long lines here and easier to review. This is what I have seen in other specs as well and people seem to be generally happy with it.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

regarding the default value: this matches what touch events level 2 says https://w3c.github.io/touch-events/#dom-touch-altitudeangle

i've also tested this on an ipad (with no stylus, just using finger) and a modified version of my HUD tracker (that explicitly still shows touch events, rather than pointer events) https://patrickhlauke.github.io/touch/tracker/multi-touch-tracker-pointer-hud-toucheventsonly.html and indeed azimuthAngle and altitudeAngle are both defined (so the props exist), and return 0. while i agree that it would be logical for the default to be pi/2, i think it's better to match touch events and what current implementations there do for defaults.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as for line length, agree...but as most other text in our spec already breaks this idea, i just went with this approach at this point. may need a good separate isolated reformatting pull request

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm just trying to follow the same logic we did for pressure. In TouchEvent spec force is also zero if the device doesn't support it. But in PointerEvents it is 0.5 as we thought it makes more sense for the apps to still work with those devices.
With the same argument I believe we should diverge from TouchEvent spec in this case as well and set azimuth to pi/2 when it is not supported by the hardware. WDYT?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry, only saw this comment after i went ahead with the merge...

while yes, we diverged for the pressure, i think here the danger / potential for confusion is greater because we went for exactly the same property names as touch events. happy to revisit though if you/others feel strongly about it.

@patrickhlauke patrickhlauke Apr 23, 2020

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe altitudeAngle of 0 is simply assumed as being the default / not supported value because it's impossible (i assume) to really have that situation, since when the stylus is truly resting fully flat on the surface, the stylus tip isn't touching the screen itself so wouldn't be detectable anyway (unless you have a stylus that is "hover-capable" like, say, the pen on the galaxy note). i assume the apple pencil doesn't do hovering, in which case this assumption of "zero can't be the 'real' value" may make sense

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

confirmed (as i don't have a device myself) here https://twitter.com/yatil/status/1253658517241765895

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I remember another reason for not exposing a "not supported" value was that developers didn't need to check for it every time. They just use the attribute and it works reasonably.
However, if we do go down the route of exposing a value that developers need to check for not supported case and do something other than what they would do with the actual value we are exposing I personally prefer to be much more explicit on that. Like expose an N/A or something that is clearly outside the range of possible values. I agree that Apple pen doesn't send 0 altitude at this point but I think we should be future proofing the API as well.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question is if even for non-apple-pencil stylus scenarios if there's a situation where a completely flat stylus would be detected or not.

i'm just thinking that IF there are web apps already dealing with altitudeAngle for touch events/pencil, they'd be familiar with the "if it's zero, just ignore this altogether" scenario. and because we use the exact same property name in PE, it may cause more confusion / need for forking code if in PE we then have different default.

either way yes, we need to document this more clearly either way, and also look at the pseudocode to check for that edge case/scenario.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

split this out into #320

</dd>
<dt><dfn>azimuthAngle</dfn></dt>
<dd>
<p>The azimuth angle (in radians) of the transducer (e.g. pen stylus), in the range [0, 2π] - where 0 represents a transducer whose cap is pointing in the direction of increasing X values (point to "3 o'clock" if looking straight down) on the X-Y plane, and the values progressively increase when going clockwise (π/2 at "6 o'clock", π at "9 o'clock", 3π/2 at "12 o'clock"). When the transducer is perfectly perpendicular to the surface (<code>altitudeAngle</code> of π/2), the value MUST be 0. For hardware and platforms that do not report tilt or angle, the value MUST be 0.</p>
</dd>
<dt><dfn>pointerType</dfn></dt>
<dd>
<p>Indicates the device type that caused the event (mouse, pen, touch, etc.). If a user agent is to <a>fire a pointer event</a> for a mouse, pen stylus, or touch input device, then the value of <code>pointerType</code> MUST be according to the following table:</p>
Expand Down Expand Up @@ -405,6 +417,9 @@ <h2><code>PointerEvent</code> Interface</h2>

<p>The <dfn><code>PointerEventInit</code></dfn> dictionary is used by the <dfn><code>PointerEvent</code></dfn> interface's constructor to provide a mechanism by which to construct untrusted (synthetic) pointer events. It inherits from the {{MouseEventInit}} dictionary defined in [[UI-EVENTS]]. The steps for constructing an event are defined in [[DOM]]. See the <a href="#examples" title="examples">examples</a> for sample code demonstrating how to fire an untrusted pointer event.</p>

<div class-"note">Pointer Events include two complementary sets of attributes to express the orientation of a transducer relative the X-Z plane: <dfn><code>tiltX</code></dfn> / <dfn><code>tiltY</code></dfn> (introduced in the original Pointer Events specification), and <dfn><code>altitudeAngle</code></dfn> / <dfn><code>azimuthAngle</code></dfn> (adopted from the <a href="https://w3c.github.io/touch-events/">Touch Events - Level 2</a> specification).
Implementations MUST support both sets of attributes. When an untrusted (synthetic) Pointer Event is created programmatically using the constructor, and only one set of values is provided, the complementary set of attributes MUST be calculated and initialized.</div>

<p>A <a>PointerEvent</a> has an associated <dfn>coalesced event list</dfn> (a list of
zero or more <code>PointerEvents</code>). If this event is a <code>pointermove</code>
or <code>pointerrawupdate</code> event, it is a sequence of all <code>PointerEvent</code>
Expand Down Expand Up @@ -1177,6 +1192,121 @@ <h2>Mapping for devices that do not support hover</h2>
</div>
</section>
</section>
<section class='informative'>
<h2>Converting between <code>tiltX</code> / <code>tiltY</code> and <code>altitudeAngle</code> / <code>azimuthAngle</code></h2>
<p>Depending on the specific hardware and platform, user agents will likely only receive one set of values for the transducer orientation relative to the screen plane - either <code>tiltX</code> / <code>tiltY</code> or <code>altitudeAngle</code> / <code>azimuthAngle</code>. The following basic code provides an initial suggested approach for converting these values.</p>
<pre id="example_12" class="example" title="Converting between tiltX/tiltY and altitudeAngle/azimuthAngle"><code>/* Converting between tiltX/tiltY and altitudeAngle/azimuthAngle */

function spherical2tilt(altitudeAngle, azimuthAngle){
Comment thread
patrickhlauke marked this conversation as resolved.
let radToDeg = 180/Math.PI;

let tiltXrad = 0;
let tiltYrad = 0;

if(altitudeAngle == 0){
// the pen is in the X-Y plane
if(azimuthAngle == 0 || azimuthAngle == 2*Math.PI){
// pen is on positive X axis
tiltXrad = Math.PI/2;
}
if(azimuthAngle == Math.PI/2){
// pen is on positive Y axis
tiltYrad = Math.PI/2;
}
if(azimuthAngle == Math.PI){
// pen is on negative X axis
tiltXrad = -Math.PI/2;
}
if(azimuthAngle == 3*Math.PI/2){
// pen is on negative Y axis
tiltYrad = -Math.PI/2;
}
if(azimuthAngle&gt;0 && azimuthAngle&lt;Math.PI/2){
tiltXrad = Math.PI/2;
tiltYrad = Math.PI/2;
}
if(azimuthAngle&gt;Math.PI/2 && azimuthAngle&lt;Math.PI){
tiltXrad = -Math.PI/2;
tiltYrad = Math.PI/2;
}
if(azimuthAngle&gt;Math.PI && azimuthAngle&lt;3*Math.PI/2){
tiltXrad = -Math.PI/2;
tiltYrad = -Math.PI/2;
}
if(azimuthAngle&gt;3*Math.PI/2 && azimuthAngle&lt;2*Math.PI){
tiltXrad = Math.PI/2;
tiltYrad = -Math.PI/2;
}
}

if(altitudeAngle != 0){
let tanAlt = Math.tan(altitudeAngle);

tiltXrad = Math.atan(Math.cos(azimuthAngle) / tanAlt);
tiltYrad = Math.atan(Math.sin(azimuthAngle) / tanAlt);
}

return {"tiltX":tiltXrad*radToDeg, "tiltY":tiltYrad*radToDeg};
}

function tilt2spherical(tiltX, tiltY){
let tiltXrad = tiltX * Math.PI/180;
let tiltYrad = tiltY * Math.PI/180;

let azimuthAngle = 0;

if(tiltXrad == 0){
if(tiltYrad &gt; 0){
azimuthAngle = Math.PI/2;
}
else if(tiltYrad &lt; 0){
azimuthAngle = 3*Math.PI/2;
}
}

if(tiltYrad == 0){
if(tiltXrad &lt; 0){
azimuthAngle = Math.PI;
}
}

if(Math.abs(tiltXrad) == Math.PI/2){
// not enough information to calculate azimuth
azimuthAngle = 0;
}


if(tiltXrad != 0 && tiltYrad != 0 &&
Math.abs(tiltXrad) != Math.PI/2 && Math.abs(tiltYrad) != Math.PI/2){
let tanX = Math.tan(tiltXrad);
let tanY = Math.tan(tiltYrad);

azimuthAngle = Math.atan2(tanY, tanX);
if(azimuthAngle &lt; 0){
azimuthAngle += 2*Math.PI;
}
}

let altitudeAngle = 0;

if (Math.abs(tiltXrad) == Math.PI/2){
altitudeAngle = 0
}
else if (tiltXrad == 0){
altitudeAngle = Math.PI/2 - Math.abs(tiltYrad);
}
else if(tiltYrad == 0){
altitudeAngle = Math.PI/2 - Math.abs(tiltXrad);
}

if(tiltXrad != 0 && tiltYrad != 0 && Math.abs(tiltXrad) != Math.PI/2){
altitudeAngle = Math.atan(1.0/Math.sqrt(Math.pow(Math.tan(tiltXrad),2) + Math.pow(Math.tan(tiltYrad),2)));
}

return {"altitudeAngle":altitudeAngle, "azimuthAngle":azimuthAngle};
}</code>
</pre>
</section>
<section>
<h2>Security and privacy considerations</h2>
<p>This appendix discusses security and privacy considerations for Pointer Events implementations. The discussion is limited to security and privacy issues that arise directly from implementation of the event model, APIs and events defined in this specification.</p>
Expand Down