Support cubic Béziers for fonts with CFF outlines

This commit is contained in:
Patrick Walton 2017-02-16 19:33:08 -08:00
parent a508322f34
commit 6ac37c5085
7 changed files with 107 additions and 51 deletions

View File

@ -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
@ -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)));
}
@ -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;
@ -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;
}

View File

@ -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
@ -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.
@ -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

View File

@ -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;
@ -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;
@ -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
}
@ -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
}
@ -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
}
@ -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
@ -272,7 +272,7 @@ 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;
@ -280,14 +280,14 @@ impl<'a> CffTable<'a> {
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
@ -311,7 +311,7 @@ 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;
@ -319,14 +319,14 @@ impl<'a> CffTable<'a> {
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
@ -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;
@ -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;
@ -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,
});
}
@ -572,7 +572,7 @@ 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;
@ -580,7 +580,7 @@ fn process_hvcurveto_h<F>(chunk: &[i32],
callback(&Point {
position: *pos,
index_in_contour: *index_in_contour + 1,
on_curve: true,
kind: PointKind::SecondCubicControl,
});
pos.x += dxf as i16;
@ -588,7 +588,7 @@ fn process_hvcurveto_h<F>(chunk: &[i32],
callback(&Point {
position: *pos,
index_in_contour: *index_in_contour + 2,
on_curve: true,
kind: PointKind::OnCurve,
});
*index_in_contour += 3
@ -604,7 +604,7 @@ 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;
@ -612,7 +612,7 @@ fn process_hvcurveto_v<F>(chunk: &[i32],
callback(&Point {
position: *pos,
index_in_contour: *index_in_contour + 1,
on_curve: true,
kind: PointKind::SecondCubicControl,
});
pos.x += chunk[3] as i16;
@ -620,7 +620,7 @@ fn process_hvcurveto_v<F>(chunk: &[i32],
callback(&Point {
position: *pos,
index_in_contour: *index_in_contour + 2,
on_curve: true,
kind: PointKind::OnCurve,
});
*index_in_contour += 3

View File

@ -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.
@ -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
}
@ -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
@ -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
@ -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,
})
}

View File

@ -10,11 +10,13 @@
//! OpenType fonts.
pub use otf::glyf::{Point, PointKind};
use byteorder::{BigEndian, ReadBytesExt};
use charmap::{CodepointRange, GlyphMapping};
use otf::cff::CffTable;
use otf::cmap::CmapTable;
use otf::glyf::{GlyfTable, Point};
use otf::glyf::GlyfTable;
use otf::head::HeadTable;
use otf::hhea::HheaTable;
use otf::hmtx::{HmtxTable, HorizontalMetrics};

View File

@ -14,7 +14,7 @@ use error::GlError;
use euclid::Size2D;
use gl::types::{GLsizeiptr, GLuint};
use gl;
use otf::{self, Font};
use otf::{self, Font, PointKind};
use std::mem;
use std::os::raw::c_void;
@ -50,7 +50,7 @@ impl OutlineBuilder {
let mut point_index = self.vertices.len() as u32;
let start_index = self.indices.len() as u32;
let start_point = point_index;
let mut last_point_on_curve = true;
let mut last_point_kind = PointKind::OnCurve;
try!(font.for_each_point(glyph_id, |point| {
self.vertices.push(Vertex {
@ -59,17 +59,22 @@ impl OutlineBuilder {
glyph_index: glyph_index,
});
if point.index_in_contour > 0 && point.on_curve {
let indices = if !last_point_on_curve {
[point_index - 2, point_index - 1, point_index]
} else {
[point_index - 1, 0, point_index]
if point.index_in_contour > 0 && point.kind == PointKind::OnCurve {
let indices = match last_point_kind {
PointKind::FirstCubicControl => [0, 0, 0, 0],
PointKind::SecondCubicControl => {
[point_index - 3, point_index - 2, point_index - 1, point_index]
}
PointKind::QuadControl => {
[point_index - 2, point_index - 1, point_index - 1, point_index]
}
PointKind::OnCurve => [point_index - 1, 0, 0, point_index],
};
self.indices.extend(indices.iter().cloned());
}
point_index += 1;
last_point_on_curve = point.on_curve
last_point_kind = point.kind
}));
// Add a glyph descriptor.

View File

@ -273,7 +273,7 @@ impl Rasterizer {
gl::Uniform2ui(self.draw_atlas_size_uniform, rect.size.width, rect.size.height);
gl::PatchParameteri(gl::PATCH_VERTICES, 3);
gl::PatchParameteri(gl::PATCH_VERTICES, 4);
// Use blending on our floating point framebuffer to accumulate coverage.
gl::Enable(gl::BLEND);