servo/components/util/geometry.rs
Patrick Walton a4a9a46a87 gfx: Rewrite display list construction to make stacking-contexts more
first-class.

This implements the scheme described here:

    https://groups.google.com/forum/#!topic/mozilla.dev.servo/sZVPSfPVfkg

This commit changes Servo to generate one display list per stacking
context instead of one display list per layer. This is purely a
refactoring; there are no functional changes. Performance is essentially
the same as before. However, there should be numerous future benefits
that this is intended to allow for:

* It makes the code simpler to understand because the "new layer needed"
  vs. "no new layer needed" code paths are more consolidated.

* It makes it easy to support CSS properties that did not fit into our
  previous flat display list model (without unconditionally layerizing
  them):

  o `opacity` should be easy to support because the stacking context
    provides the higher-level grouping of display items to which opacity
    is to be applied.

  o `transform` can be easily supported because the stacking context
    provides a place to stash the transformation matrix. This has the side
    benefit of nicely separating the transformation matrix from the
    clipping regions.

* The `flatten` logic is now O(1) instead of O(n) and now only needs to
  be invoked for pseudo-stacking contexts (right now: just floats),
  instead of for every stacking context.

* Layers are now a proper tree instead of a flat list as far as layout
  is concerned, bringing us closer to a production-quality
  compositing/layers framework.

* This commit opens the door to incremental display list construction at
  the level of stacking contexts.

Future performance improvements could come from optimizing allocation of
display list items, and, of course, incremental display list
construction.
2014-11-14 17:31:15 -08:00

327 lines
8.4 KiB
Rust

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use geom::length::Length;
use geom::point::Point2D;
use geom::rect::Rect;
use geom::size::Size2D;
use serialize::{Encodable, Encoder};
use std::default::Default;
use std::i32;
use std::num::{NumCast, Zero};
use std::fmt;
// Units for use with geom::length and geom::scale_factor.
/// A normalized "pixel" at the default resolution for the display.
///
/// Like the CSS "px" unit, the exact physical size of this unit may vary between devices, but it
/// should approximate a device-independent reference length. This unit corresponds to Android's
/// "density-independent pixel" (dip), Mac OS X's "point", and Windows "device-independent pixel."
///
/// The relationship between DevicePixel and ScreenPx is defined by the OS. On most low-dpi
/// screens, one ScreenPx is equal to one DevicePixel. But on high-density screens it can be
/// some larger number. For example, by default on Apple "retina" displays, one ScreenPx equals
/// two DevicePixels. On Android "MDPI" displays, one ScreenPx equals 1.5 device pixels.
///
/// The ratio between ScreenPx and DevicePixel for a given display be found by calling
/// `servo::windowing::WindowMethods::hidpi_factor`.
#[deriving(Show)]
pub enum ScreenPx {}
/// One CSS "px" in the coordinate system of the "initial viewport":
/// http://www.w3.org/TR/css-device-adapt/#initial-viewport
///
/// ViewportPx is equal to ScreenPx times a "page zoom" factor controlled by the user. This is
/// the desktop-style "full page" zoom that enlarges content but then reflows the layout viewport
/// so it still exactly fits the visible area.
///
/// At the default zoom level of 100%, one PagePx is equal to one ScreenPx. However, if the
/// document is zoomed in or out then this scale may be larger or smaller.
#[deriving(Encodable, Show)]
pub enum ViewportPx {}
/// One CSS "px" in the root coordinate system for the content document.
///
/// PagePx is equal to ViewportPx multiplied by a "viewport zoom" factor controlled by the user.
/// This is the mobile-style "pinch zoom" that enlarges content without reflowing it. When the
/// viewport zoom is not equal to 1.0, then the layout viewport is no longer the same physical size
/// as the viewable area.
#[deriving(Encodable, Show)]
pub enum PagePx {}
// In summary, the hierarchy of pixel units and the factors to convert from one to the next:
//
// DevicePixel
// / hidpi_ratio => ScreenPx
// / desktop_zoom => ViewportPx
// / pinch_zoom => PagePx
// An Au is an "App Unit" and represents 1/60th of a CSS pixel. It was
// originally proposed in 2002 as a standard unit of measure in Gecko.
// See https://bugzilla.mozilla.org/show_bug.cgi?id=177805 for more info.
//
// FIXME: Implement Au using Length and ScaleFactor instead of a custom type.
#[deriving(Clone, PartialEq, PartialOrd, Eq, Ord, Zero)]
pub struct Au(pub i32);
impl Default for Au {
#[inline]
fn default() -> Au {
Au(0)
}
}
pub static ZERO_POINT: Point2D<Au> = Point2D {
x: Au(0),
y: Au(0),
};
pub static ZERO_RECT: Rect<Au> = Rect {
origin: Point2D {
x: Au(0),
y: Au(0),
},
size: Size2D {
width: Au(0),
height: Au(0),
}
};
pub static MAX_RECT: Rect<Au> = Rect {
origin: Point2D {
x: Au(i32::MIN / 2),
y: Au(i32::MIN / 2),
},
size: Size2D {
width: MAX_AU,
height: MAX_AU,
}
};
pub const MIN_AU: Au = Au(i32::MIN);
pub const MAX_AU: Au = Au(i32::MAX);
impl<E, S: Encoder<E>> Encodable<S, E> for Au {
fn encode(&self, e: &mut S) -> Result<(), E> {
e.emit_f64(to_frac_px(*self))
}
}
impl fmt::Show for Au {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}px", to_frac_px(*self))
}}
impl Add<Au,Au> for Au {
#[inline]
fn add(&self, other: &Au) -> Au {
let Au(s) = *self;
let Au(o) = *other;
Au(s + o)
}
}
impl Sub<Au,Au> for Au {
#[inline]
fn sub(&self, other: &Au) -> Au {
let Au(s) = *self;
let Au(o) = *other;
Au(s - o)
}
}
impl Mul<i32, Au> for Au {
#[inline]
fn mul(&self, other: &i32) -> Au {
let Au(s) = *self;
Au(s * *other)
}
}
impl Div<i32, Au> for Au {
#[inline]
fn div(&self, other: &i32) -> Au {
let Au(s) = *self;
Au(s / *other)
}
}
impl Rem<i32, Au> for Au {
#[inline]
fn rem(&self, other: &i32) -> Au {
let Au(s) = *self;
Au(s % *other)
}
}
impl Neg<Au> for Au {
#[inline]
fn neg(&self) -> Au {
let Au(s) = *self;
Au(-s)
}
}
impl NumCast for Au {
#[inline]
fn from<T:ToPrimitive>(n: T) -> Option<Au> {
Some(Au(n.to_i32().unwrap()))
}
}
impl ToPrimitive for Au {
#[inline]
fn to_i64(&self) -> Option<i64> {
let Au(s) = *self;
Some(s as i64)
}
#[inline]
fn to_u64(&self) -> Option<u64> {
let Au(s) = *self;
Some(s as u64)
}
#[inline]
fn to_f32(&self) -> Option<f32> {
let Au(s) = *self;
s.to_f32()
}
#[inline]
fn to_f64(&self) -> Option<f64> {
let Au(s) = *self;
s.to_f64()
}
}
impl Au {
/// FIXME(pcwalton): Workaround for lack of cross crate inlining of newtype structs!
#[inline]
pub fn new(value: i32) -> Au {
Au(value)
}
#[inline]
pub fn scale_by(self, factor: f64) -> Au {
let Au(s) = self;
Au(((s as f64) * factor) as i32)
}
#[inline]
pub fn from_px(px: int) -> Au {
NumCast::from(px * 60).unwrap()
}
#[inline]
pub fn from_page_px(px: Length<PagePx, f32>) -> Au {
NumCast::from(px.get() * 60f32).unwrap()
}
#[inline]
pub fn to_nearest_px(&self) -> int {
let Au(s) = *self;
((s as f64) / 60f64).round() as int
}
#[inline]
pub fn to_subpx(&self) -> f64 {
let Au(s) = *self;
(s as f64) / 60f64
}
#[inline]
pub fn to_snapped(&self) -> Au {
let Au(s) = *self;
let res = s % 60i32;
return if res >= 30i32 { return Au(s - res + 60i32) }
else { return Au(s - res) };
}
#[inline]
pub fn from_frac32_px(px: f32) -> Au {
Au((px * 60f32) as i32)
}
#[inline]
pub fn from_pt(pt: f64) -> Au {
from_frac_px(pt_to_px(pt))
}
#[inline]
pub fn from_frac_px(px: f64) -> Au {
Au((px * 60f64) as i32)
}
#[inline]
pub fn min(x: Au, y: Au) -> Au {
let Au(xi) = x;
let Au(yi) = y;
if xi < yi { x } else { y }
}
#[inline]
pub fn max(x: Au, y: Au) -> Au {
let Au(xi) = x;
let Au(yi) = y;
if xi > yi { x } else { y }
}
}
// assumes 72 points per inch, and 96 px per inch
pub fn pt_to_px(pt: f64) -> f64 {
pt / 72f64 * 96f64
}
// assumes 72 points per inch, and 96 px per inch
pub fn px_to_pt(px: f64) -> f64 {
px / 96f64 * 72f64
}
pub fn from_frac_px(px: f64) -> Au {
Au((px * 60f64) as i32)
}
pub fn from_px(px: int) -> Au {
NumCast::from(px * 60).unwrap()
}
pub fn to_px(au: Au) -> int {
let Au(a) = au;
(a / 60) as int
}
pub fn to_frac_px(au: Au) -> f64 {
let Au(a) = au;
(a as f64) / 60f64
}
// assumes 72 points per inch, and 96 px per inch
pub fn from_pt(pt: f64) -> Au {
from_px((pt / 72f64 * 96f64) as int)
}
// assumes 72 points per inch, and 96 px per inch
pub fn to_pt(au: Au) -> f64 {
let Au(a) = au;
(a as f64) / 60f64 * 72f64 / 96f64
}
/// Returns true if the rect contains the given point. Points on the top or left sides of the rect
/// are considered inside the rectangle, while points on the right or bottom sides of the rect are
/// not considered inside the rectangle.
pub fn rect_contains_point<T:PartialOrd + Add<T,T>>(rect: Rect<T>, point: Point2D<T>) -> bool {
point.x >= rect.origin.x && point.x < rect.origin.x + rect.size.width &&
point.y >= rect.origin.y && point.y < rect.origin.y + rect.size.height
}
/// A helper function to convert a rect of `f32` pixels to a rect of app units.
pub fn f32_rect_to_au_rect(rect: Rect<f32>) -> Rect<Au> {
Rect(Point2D(Au::from_frac32_px(rect.origin.x), Au::from_frac32_px(rect.origin.y)),
Size2D(Au::from_frac32_px(rect.size.width), Au::from_frac32_px(rect.size.height)))
}