Skip to content

Commit

Permalink
Support cubic Béziers for fonts with CFF outlines
Browse files Browse the repository at this point in the history
  • Loading branch information
pcwalton committed Feb 17, 2017
1 parent a508322 commit 6ac37c5
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 51 deletions.
20 changes: 14 additions & 6 deletions resources/shaders/draw.tcs.glsl
Expand Up @@ -22,10 +22,13 @@ flat in int vVertexID[];

// The starting point of the segment.
out vec2 vpP0[];
// The control point, if this is a curve. If this is a line, this value must be ignored.
// The first control point, if this is a curve. If this is a line, this value must be ignored.
out vec2 vpP1[];
// The endpoint of this segment.
// The second control point, if this is a curve. If this is a line, this value must be ignored.
// If this curve is quadratic, this will be the same as `vpP1`.
out vec2 vpP2[];
// The endpoint of this segment.
out vec2 vpP3[];
// The tessellation level.
//
// This is passed along explicitly instead of having the TES read it from `gl_TessLevelInner` in
Expand All @@ -36,15 +39,19 @@ void main() {
vec2 p0 = gl_in[0].gl_Position.xy;
vec2 p1 = gl_in[1].gl_Position.xy;
vec2 p2 = gl_in[2].gl_Position.xy;
vec2 p3 = gl_in[3].gl_Position.xy;

// Divide into lines.
float lineCount = 1.0f;
if (vVertexID[1] > 0) {
// Quadratic curve.
vec2 dev = p0 - 2.0f * p1 + p2;
// A curve.
//
// FIXME(pcwalton): Is this formula good for cubic curves?
vec2 dev = p0 - 2.0f * mix(p1, p2, 0.5) + p3;
float devSq = dot(dev, dev);
if (devSq >= CURVE_THRESHOLD) {
// Inverse square root is likely no slower and may be faster than regular square root
// Inverse square root is likely no slower and may be faster than regular square
// root
// (e.g. on x86).
lineCount += floor(inversesqrt(inversesqrt(CURVE_TOLERANCE * devSq)));
}
Expand Down Expand Up @@ -92,7 +99,7 @@ void main() {
// so we're in the clear: the rasterizer will always discard the unshaded areas and render only
// the shaded ones.

float tessLevel = min(p0.x == p2.x ? 0.0f : (lineCount * 2.0f - 1.0f), 31.0f);
float tessLevel = min(p0.x == p3.x ? 0.0f : (lineCount * 2.0f - 1.0f), 31.0f);
gl_TessLevelInner[0] = tessLevel;
gl_TessLevelInner[1] = 1.0f;
gl_TessLevelOuter[0] = 1.0f;
Expand All @@ -105,6 +112,7 @@ void main() {
vpP0[gl_InvocationID] = p0;
vpP1[gl_InvocationID] = p1;
vpP2[gl_InvocationID] = p2;
vpP3[gl_InvocationID] = p3;
vpTessLevel[gl_InvocationID] = tessLevel;
}

28 changes: 22 additions & 6 deletions resources/shaders/draw.tes.glsl
Expand Up @@ -17,10 +17,13 @@ uniform uvec2 uAtlasSize;

// The starting point of the segment.
in vec2 vpP0[];
// The control point, if this is a curve. If this is a line, this value must be ignored.
// The first control point, if this is a curve. If this is a line, this value must be ignored.
in vec2 vpP1[];
// The endpoint of this segment.
// The second control point, if this is a cubic curve. If this is a quadratic curve or a line, this
// is equal to `vpP1`.
in vec2 vpP2[];
// The endpoint of this segment.
in vec2 vpP3[];
// The tessellation level.
//
// This is passed along explicitly instead of having the TES read it from `gl_TessLevelInner` in
Expand All @@ -40,7 +43,7 @@ flat out vec2 vYMinMax;

void main() {
// Read in curve points.
vec2 cP0 = vpP0[0], cP1 = vpP1[0], cP2 = vpP2[0];
vec2 cP0 = vpP0[0], cP1 = vpP1[0], cP2 = vpP2[0], cP3 = vpP3[0];

// Work out how many lines made up this segment, which line we're working on, and which
// endpoint of that line we're looking at.
Expand All @@ -53,12 +56,25 @@ void main() {
vec2 p0, p1;
if (lineCount == 1) {
p0 = cP0;
p1 = cP2;
p1 = cP3;
} else {
float t0 = float(lineIndex + 0) / float(lineCount);
float t1 = float(lineIndex + 1) / float(lineCount);
p0 = mix(mix(cP0, cP1, t0), mix(cP1, cP2, t0), t0);
p1 = mix(mix(cP0, cP1, t1), mix(cP1, cP2, t1), t1);

// These lerps are needed both for quadratic and cubic Béziers.
vec2 pP0P1T0 = mix(cP0, cP1, t0), pP0P1T1 = mix(cP0, cP1, t1);
vec2 pP2P3T0 = mix(cP2, cP3, t0), pP2P3T1 = mix(cP2, cP3, t1);

if (cP1 == cP2) {
// Quadratic Bézier.
p0 = mix(pP0P1T0, pP2P3T0, t0);
p1 = mix(pP0P1T1, pP2P3T1, t1);
} else {
// Cubic Bézier.
vec2 pP1P2T0 = mix(cP1, cP2, t0), pP1P2T1 = mix(cP1, cP2, t1);
p0 = mix(mix(pP0P1T0, pP1P2T0, t0), mix(pP1P2T0, pP2P3T0, t0), t0);
p1 = mix(mix(pP0P1T1, pP1P2T1, t1), mix(pP1P2T1, pP2P3T1, t1), t1);
}
}

// Compute direction. Flip the two points around so that p0 is on the left and p1 is on the
Expand Down
46 changes: 23 additions & 23 deletions src/otf/cff.rs
Expand Up @@ -10,7 +10,7 @@

use byteorder::{BigEndian, LittleEndian, ReadBytesExt};
use euclid::Point2D;
use otf::glyf::Point;
use otf::glyf::{Point, PointKind};
use otf::head::HeadTable;
use otf::{Error, FontTable};
use outline::GlyphBounds;
Expand Down Expand Up @@ -112,7 +112,7 @@ impl<'a> CffTable<'a> {
callback(&Point {
position: pos,
index_in_contour: 0,
on_curve: true,
kind: PointKind::OnCurve,
});
start = pos;
index_in_contour = 1;
Expand All @@ -125,7 +125,7 @@ impl<'a> CffTable<'a> {
callback(&Point {
position: pos,
index_in_contour: index_in_contour,
on_curve: true,
kind: PointKind::OnCurve,
});
index_in_contour += 1
}
Expand All @@ -143,7 +143,7 @@ impl<'a> CffTable<'a> {
callback(&Point {
position: pos,
index_in_contour: index_in_contour,
on_curve: true,
kind: PointKind::OnCurve,
});
index_in_contour += 1
}
Expand All @@ -161,7 +161,7 @@ impl<'a> CffTable<'a> {
callback(&Point {
position: pos,
index_in_contour: index_in_contour,
on_curve: true,
kind: PointKind::OnCurve,
});
index_in_contour += 1
}
Expand All @@ -174,21 +174,21 @@ impl<'a> CffTable<'a> {
callback(&Point {
position: pos,
index_in_contour: index_in_contour + 0,
on_curve: true,
kind: PointKind::FirstCubicControl,
});

pos = pos + Point2D::new(chunk[2] as i16, chunk[3] as i16);
callback(&Point {
position: pos,
index_in_contour: index_in_contour + 1,
on_curve: true,
kind: PointKind::SecondCubicControl,
});

pos = pos + Point2D::new(chunk[4] as i16, chunk[5] as i16);
callback(&Point {
position: pos,
index_in_contour: index_in_contour + 2,
on_curve: true,
kind: PointKind::OnCurve,
});

index_in_contour += 3
Expand Down Expand Up @@ -272,22 +272,22 @@ impl<'a> CffTable<'a> {
callback(&Point {
position: pos,
index_in_contour: index_in_contour,
on_curve: true,
kind: PointKind::FirstCubicControl,
});

pos.x += chunk[1] as i16;
pos.y += chunk[2] as i16;
callback(&Point {
position: pos,
index_in_contour: index_in_contour + 1,
on_curve: true,
kind: PointKind::SecondCubicControl,
});

pos.y += chunk[3] as i16;
callback(&Point {
position: pos,
index_in_contour: index_in_contour + 2,
on_curve: true,
kind: PointKind::OnCurve,
});

index_in_contour += 3
Expand All @@ -311,22 +311,22 @@ impl<'a> CffTable<'a> {
callback(&Point {
position: pos,
index_in_contour: index_in_contour,
on_curve: true,
kind: PointKind::FirstCubicControl,
});

pos.x += chunk[1] as i16;
pos.y += chunk[2] as i16;
callback(&Point {
position: pos,
index_in_contour: index_in_contour + 1,
on_curve: true,
kind: PointKind::SecondCubicControl,
});

pos.x += chunk[3] as i16;
callback(&Point {
position: pos,
index_in_contour: index_in_contour + 2,
on_curve: true,
kind: PointKind::OnCurve,
});

index_in_contour += 3
Expand Down Expand Up @@ -367,7 +367,7 @@ impl<'a> CffTable<'a> {
callback(&Point {
position: pos,
index_in_contour: 0,
on_curve: true,
kind: PointKind::OnCurve,
});
start = pos;
index_in_contour = 1;
Expand All @@ -380,7 +380,7 @@ impl<'a> CffTable<'a> {
callback(&Point {
position: pos,
index_in_contour: 0,
on_curve: true,
kind: PointKind::OnCurve,
});
start = pos;
index_in_contour = 1;
Expand Down Expand Up @@ -558,7 +558,7 @@ fn close_path_if_necessary<F>(pos: &mut Point2D<i16>,
callback(&Point {
position: *start,
index_in_contour: index_in_contour,
on_curve: true,
kind: PointKind::OnCurve,
});
}

Expand All @@ -572,23 +572,23 @@ fn process_hvcurveto_h<F>(chunk: &[i32],
callback(&Point {
position: *pos,
index_in_contour: *index_in_contour + 0,
on_curve: true,
kind: PointKind::FirstCubicControl,
});

pos.x += chunk[1] as i16;
pos.y += chunk[2] as i16;
callback(&Point {
position: *pos,
index_in_contour: *index_in_contour + 1,
on_curve: true,
kind: PointKind::SecondCubicControl,
});

pos.x += dxf as i16;
pos.y += chunk[3] as i16;
callback(&Point {
position: *pos,
index_in_contour: *index_in_contour + 2,
on_curve: true,
kind: PointKind::OnCurve,
});

*index_in_contour += 3
Expand All @@ -604,23 +604,23 @@ fn process_hvcurveto_v<F>(chunk: &[i32],
callback(&Point {
position: *pos,
index_in_contour: *index_in_contour + 0,
on_curve: true,
kind: PointKind::FirstCubicControl,
});

pos.x += chunk[1] as i16;
pos.y += chunk[2] as i16;
callback(&Point {
position: *pos,
index_in_contour: *index_in_contour + 1,
on_curve: true,
kind: PointKind::SecondCubicControl,
});

pos.x += chunk[3] as i16;
pos.y += dyf as i16;
callback(&Point {
position: *pos,
index_in_contour: *index_in_contour + 2,
on_curve: true,
kind: PointKind::OnCurve,
});

*index_in_contour += 3
Expand Down
37 changes: 31 additions & 6 deletions src/otf/glyf.rs
Expand Up @@ -46,9 +46,30 @@ bitflags! {

#[derive(Clone, Copy, PartialEq, Debug)]
pub struct Point {
/// Where the point is located in glyph space.
pub position: Point2D<i16>,

/// The index of the point in this contour.
///
/// When iterating over points via `for_each_point`, a value of 0 here indicates that a new
/// contour begins.
pub index_in_contour: u16,
pub on_curve: bool,

/// The kind of point this is.
pub kind: PointKind,
}

/// The type of point.
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum PointKind {
/// The point is on the curve.
OnCurve,
/// The point is a quadratic control point.
QuadControl,
/// The point is the first cubic control point.
FirstCubicControl,
/// The point is the second cubic control point.
SecondCubicControl,
}

/// TODO(pcwalton): Add some caching so we don't keep going to the `loca` table all the time.
Expand Down Expand Up @@ -166,7 +187,7 @@ impl<'a> GlyfTable<'a> {
callback(&Point {
position: position,
index_in_contour: point_index_in_contour,
on_curve: true,
kind: PointKind::OnCurve,
});
point_index_in_contour += 1
}
Expand All @@ -185,7 +206,11 @@ impl<'a> GlyfTable<'a> {
} else {
callback(&Point {
position: position,
on_curve: flags.contains(ON_CURVE),
kind: if flags.contains(ON_CURVE) {
PointKind::OnCurve
} else {
PointKind::QuadControl
},
index_in_contour: point_index_in_contour,
});
point_index_in_contour += 1
Expand All @@ -203,14 +228,14 @@ impl<'a> GlyfTable<'a> {
callback(&Point {
position: position,
index_in_contour: point_index_in_contour,
on_curve: true,
kind: PointKind::OnCurve,
});
point_index_in_contour += 1
}

callback(&Point {
position: initial_off_curve_point,
on_curve: false,
kind: PointKind::QuadControl,
index_in_contour: point_index_in_contour,
});
point_index_in_contour += 1
Expand All @@ -220,7 +245,7 @@ impl<'a> GlyfTable<'a> {
if let Some(first_on_curve_point) = first_on_curve_point {
callback(&Point {
position: first_on_curve_point,
on_curve: true,
kind: PointKind::OnCurve,
index_in_contour: point_index_in_contour,
})
}
Expand Down

0 comments on commit 6ac37c5

Please sign in to comment.