Remove Pathfinder 1 now that Pathfinder 2 supports all its features and

more
This commit is contained in:
Patrick Walton 2017-10-06 20:03:50 -07:00
parent c8284e18b2
commit 3352f869f0
59 changed files with 0 additions and 9237 deletions

View File

@ -1,30 +0,0 @@
[package]
name = "pathfinder"
version = "0.1.0"
authors = ["Patrick Walton <pcwalton@mimiga.net>"]
[dependencies]
bitflags = "0.7"
byteorder = "1"
euclid = "0.10"
flate2 = "0.2"
gl = "0.6"
memmap = "0.5"
num-traits = "0.1"
time = "0.1"
[dependencies.compute-shader]
git = "https://github.com/pcwalton/compute-shader.git"
[dev-dependencies]
bencher = "0.1"
clap = "2.20"
image = "0.12"
quickcheck = "0.4"
[dev-dependencies.glfw]
git = "https://github.com/bjz/glfw-rs.git"
[dev-dependencies.lord-drawquaad]
git = "https://github.com/pcwalton/lord-drawquaad.git"

View File

@ -1,201 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -1,25 +0,0 @@
Copyright (c) 2010 The Rust Project Developers
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

View File

@ -1,162 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
extern crate bencher;
extern crate compute_shader;
extern crate euclid;
extern crate gl;
extern crate glfw;
extern crate lord_drawquaad;
extern crate memmap;
extern crate pathfinder;
extern crate time;
use bencher::stats::{self, Stats};
use compute_shader::buffer;
use compute_shader::image::Format;
use compute_shader::instance::Instance;
use euclid::{Point2D, Rect, Size2D};
use gl::types::GLuint;
use glfw::{Context, OpenGlProfileHint, WindowHint, WindowMode};
use memmap::{Mmap, Protection};
use pathfinder::atlas::{AtlasBuilder, AtlasOptions, GlyphRasterizationOptions};
use pathfinder::charmap::CodepointRange;
use pathfinder::coverage::{CoverageBuffer, CoverageBufferOptions};
use pathfinder::font::Font;
use pathfinder::outline::OutlineBuilder;
use pathfinder::rasterizer::{Rasterizer, RasterizerOptions};
use std::env;
use std::os::raw::c_void;
use std::path::PathBuf;
const ATLAS_SIZE: u32 = 2048;
const WIDTH: u32 = 512;
const HEIGHT: u32 = 384;
const MIN_TIME_PER_SIZE: u64 = 300_000_000;
const MAX_TIME_PER_SIZE: u64 = 3_000_000_000;
static SHADER_PATH: &'static str = "resources/shaders/";
fn main() {
let mut glfw = glfw::init(glfw::LOG_ERRORS).unwrap();
glfw.window_hint(WindowHint::ContextVersion(3, 3));
glfw.window_hint(WindowHint::OpenGlForwardCompat(true));
glfw.window_hint(WindowHint::OpenGlProfile(OpenGlProfileHint::Core));
let context = glfw.create_window(WIDTH, HEIGHT, "generate-atlas", WindowMode::Windowed);
let (mut window, _events) = context.expect("Couldn't create a window!");
window.make_current();
gl::load_with(|symbol| window.get_proc_address(symbol) as *const c_void);
let (device_pixel_width, _) = window.get_framebuffer_size();
let instance = Instance::new().unwrap();
let device = instance.open_device().unwrap();
let queue = device.create_queue().unwrap();
let mut rasterizer_options = RasterizerOptions::from_env().unwrap();
if env::var("PATHFINDER_SHADER_PATH").is_err() {
rasterizer_options.shader_path = PathBuf::from(SHADER_PATH)
}
let rasterizer = Rasterizer::new(&instance, device, queue, rasterizer_options).unwrap();
for point_size in 6..201 {
// FIXME(pcwalton)
let shelf_height = point_size * 2;
let file = Mmap::open_path(env::args().nth(1).unwrap(), Protection::Read).unwrap();
let mut buffer = vec![];
let mut results = vec![];
let start = time::precise_time_ns();
let mut last_time = start;
let (mut outlines, mut glyph_count, mut atlas);
loop {
glyph_count = 0;
unsafe {
let font = Font::new(file.as_slice(), &mut buffer).unwrap();
let codepoint_ranges = [CodepointRange::new(' ' as u32, '~' as u32)];
let glyph_mapping = font.glyph_mapping_for_codepoint_ranges(&codepoint_ranges)
.unwrap();
let mut outline_builder = OutlineBuilder::new();
for (_, glyph_id) in glyph_mapping.iter() {
outline_builder.add_glyph(&font, glyph_id).unwrap();
glyph_count += 1
}
outlines = outline_builder.create_buffers().unwrap();
let mut atlas_builder = AtlasBuilder::new(&AtlasOptions {
available_width: device_pixel_width as GLuint,
shelf_height: shelf_height,
..AtlasOptions::default()
});
for glyph_index in 0..(glyph_count as u16) {
atlas_builder.pack_glyph(&outlines,
glyph_index,
&GlyphRasterizationOptions {
point_size: point_size as f32,
..GlyphRasterizationOptions::default()
}).unwrap();
}
atlas = atlas_builder.create_atlas().unwrap();
}
let end = time::precise_time_ns();
results.push((end - last_time) as f64);
if end - start > MAX_TIME_PER_SIZE {
break
}
last_time = end
}
stats::winsorize(&mut results, 5.0);
let time_per_glyph = results.mean() / 1_000.0 / glyph_count as f64;
println!("cpu,{}", time_per_glyph);
let atlas_size = Size2D::new(ATLAS_SIZE, ATLAS_SIZE);
let coverage_buffer = CoverageBuffer::new(rasterizer.device(),
&CoverageBufferOptions {
size: atlas_size,
..CoverageBufferOptions::default()
}).unwrap();
let image = rasterizer.device()
.create_image(Format::R8, buffer::Protection::WriteOnly, &atlas_size)
.unwrap();
let rect = Rect::new(Point2D::new(0, 0), atlas_size);
let mut results = vec![];
let start_time = time::precise_time_ns();
loop {
let events = rasterizer.draw_atlas(&image, &rect, &atlas, &outlines, &coverage_buffer)
.unwrap();
let mut draw_time = 0u64;
unsafe {
gl::GetQueryObjectui64v(events.draw, gl::QUERY_RESULT, &mut draw_time);
}
let accum_time = events.accum.time_elapsed().unwrap() as f64;
let time_per_glyph = (draw_time as f64 + accum_time as f64) /
(1000.0 * glyph_count as f64);
results.push(time_per_glyph);
let now = time::precise_time_ns();
if (now - start_time > MIN_TIME_PER_SIZE && results.median_abs_dev_pct() < 1.0) ||
now - start_time > MAX_TIME_PER_SIZE {
break
}
}
stats::winsorize(&mut results, 5.0);
let time_per_glyph = results.mean();
println!("{},{}", point_size, time_per_glyph);
}
window.set_should_close(true);
}

View File

@ -1,73 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
extern crate clap;
extern crate euclid;
extern crate memmap;
extern crate pathfinder;
use clap::{App, Arg};
use memmap::{Mmap, Protection};
use pathfinder::charmap::CodepointRange;
use pathfinder::font::{Font, PointKind};
use pathfinder::hinting::Hinter;
use std::char;
fn main() {
let hint_arg = Arg::with_name("hint").short("H")
.long("hint")
.value_name("POINT-SIZE")
.help("Apply hinting instructions for a point size")
.takes_value(true);
let font_arg = Arg::with_name("FONT-FILE").help("Select the font file (`.ttf`, `.otf`, etc.)")
.required(true)
.index(1);
let matches = App::new("dump-outlines").arg(hint_arg).arg(font_arg).get_matches();
let file = Mmap::open_path(matches.value_of("FONT-FILE").unwrap(), Protection::Read).unwrap();
let mut buffer = vec![];
unsafe {
let font = Font::new(file.as_slice(), &mut buffer).unwrap();
let hinter = if let Some(point_size) = matches.value_of("hint") {
let mut hinter = Hinter::new(&font).unwrap();
hinter.set_point_size(point_size.parse().unwrap()).unwrap();
Some(hinter)
} else {
None
};
let codepoint_ranges = [CodepointRange::new('!' as u32, '~' as u32)];
let glyph_mapping = font.glyph_mapping_for_codepoint_ranges(&codepoint_ranges).unwrap();
for (glyph_index, (_, glyph_id)) in glyph_mapping.iter().enumerate() {
let codepoint = '!' as u32 + glyph_index as u32;
println!("Glyph {}: codepoint {} '{}':",
glyph_id,
codepoint,
char::from_u32(codepoint).unwrap_or('?'));
let mut last_point_was_on_curve = false;
font.for_each_point(glyph_id, |point| {
let prefix = if point.index_in_contour == 0 {
"M "
} else {
match point.kind {
PointKind::OnCurve if last_point_was_on_curve => "L ",
PointKind::OnCurve => " ",
PointKind::QuadControl => "Q ",
PointKind::FirstCubicControl => "C ",
PointKind::SecondCubicControl => " ",
}
};
print!("{}{},{}", prefix, point.position.x, point.position.y);
last_point_was_on_curve = point.kind == PointKind::OnCurve;
if last_point_was_on_curve {
println!("")
}
}).unwrap()
}
}
}

View File

@ -1,182 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
extern crate clap;
extern crate compute_shader;
extern crate euclid;
extern crate gl;
extern crate glfw;
extern crate lord_drawquaad;
extern crate memmap;
extern crate pathfinder;
use clap::{App, Arg};
use compute_shader::buffer;
use compute_shader::image::{Color, ExternalImage, Format};
use compute_shader::instance::{Instance, ShadingLanguage};
use euclid::{Point2D, Rect, Size2D};
use gl::types::{GLint, GLuint};
use glfw::{Action, Context, Key, OpenGlProfileHint, WindowEvent, WindowHint, WindowMode};
use memmap::{Mmap, Protection};
use pathfinder::atlas::{AtlasBuilder, AtlasOptions, GlyphRasterizationOptions};
use pathfinder::charmap::CodepointRange;
use pathfinder::coverage::{CoverageBuffer, CoverageBufferOptions};
use pathfinder::font::Font;
use pathfinder::outline::OutlineBuilder;
use pathfinder::rasterizer::{Rasterizer, RasterizerOptions};
use std::env;
use std::os::raw::c_void;
use std::path::PathBuf;
const DEFAULT_POINT_SIZE: f32 = 24.0;
const WIDTH: u32 = 512;
const HEIGHT: u32 = 384;
static SHADER_PATH: &'static str = "resources/shaders/";
fn main() {
let index_arg = Arg::with_name("index").short("i")
.long("index")
.help("Select an index within a font collection")
.takes_value(true);
let subpixel_antialiasing_arg =
Arg::with_name("subpixel-aa").short("s")
.long("subpixel-aa")
.help("Enable subpixel antialiasing");
let font_arg = Arg::with_name("FONT-FILE").help("Select the font file (`.ttf`, `.otf`, etc.)")
.required(true)
.index(1);
let point_size_arg = Arg::with_name("POINT-SIZE").help("Select the point size")
.index(2);
let matches = App::new("generate-atlas").arg(index_arg)
.arg(subpixel_antialiasing_arg)
.arg(font_arg)
.arg(point_size_arg)
.get_matches();
let mut glfw = glfw::init(glfw::LOG_ERRORS).unwrap();
glfw.window_hint(WindowHint::ContextVersion(3, 3));
glfw.window_hint(WindowHint::OpenGlForwardCompat(true));
glfw.window_hint(WindowHint::OpenGlProfile(OpenGlProfileHint::Core));
let context = glfw.create_window(WIDTH, HEIGHT, "generate-atlas", WindowMode::Windowed);
let (mut window, events) = context.expect("Couldn't create a window!");
window.make_current();
gl::load_with(|symbol| window.get_proc_address(symbol) as *const c_void);
let (device_pixel_width, device_pixel_height) = window.get_framebuffer_size();
let instance = Instance::new().unwrap();
let device = instance.open_device().unwrap();
let queue = device.create_queue().unwrap();
let mut rasterizer_options = RasterizerOptions::from_env().unwrap();
if env::var("PATHFINDER_SHADER_PATH").is_err() {
rasterizer_options.shader_path = PathBuf::from(SHADER_PATH)
}
let rasterizer = Rasterizer::new(&instance, device, queue, rasterizer_options).unwrap();
let file = Mmap::open_path(matches.value_of("FONT-FILE").unwrap(), Protection::Read).unwrap();
let mut buffer = vec![];
let point_size = match matches.value_of("POINT-SIZE") {
Some(point_size) => point_size.parse().unwrap(),
None => DEFAULT_POINT_SIZE,
};
let font_index = match matches.value_of("index") {
Some(index) => index.parse().unwrap(),
None => 0,
};
let subpixel_aa = matches.is_present("subpixel-aa");
let (outlines, atlas);
unsafe {
let font = Font::from_collection_index(file.as_slice(), font_index, &mut buffer).unwrap();
let codepoint_ranges = [CodepointRange::new(' ' as u32, '~' as u32)];
let glyph_mapping = font.glyph_mapping_for_codepoint_ranges(&codepoint_ranges).unwrap();
let shelf_height = font.shelf_height(point_size);
let mut outline_builder = OutlineBuilder::new();
let mut glyph_count = 0;
for (_, glyph_id) in glyph_mapping.iter() {
outline_builder.add_glyph(&font, glyph_id).unwrap();
glyph_count += 1
}
outlines = outline_builder.create_buffers().unwrap();
let mut atlas_builder = AtlasBuilder::new(&AtlasOptions {
available_width: device_pixel_width as u32,
shelf_height: shelf_height,
subpixel_antialiasing: subpixel_aa,
});
for glyph_index in 0..glyph_count {
atlas_builder.pack_glyph(&outlines,
glyph_index,
&GlyphRasterizationOptions {
point_size: point_size,
..GlyphRasterizationOptions::default()
}).unwrap();
}
atlas = atlas_builder.create_atlas().unwrap();
}
let atlas_size = Size2D::new(device_pixel_width as GLuint, device_pixel_height as GLuint);
let coverage_buffer = CoverageBuffer::new(rasterizer.device(),
&CoverageBufferOptions {
size: atlas_size,
subpixel_antialiasing: subpixel_aa,
..CoverageBufferOptions::default()
}).unwrap();
let image = rasterizer.device()
.create_image(Format::RGBA8, buffer::Protection::ReadWrite, &atlas_size)
.unwrap();
rasterizer.queue().submit_clear(&image, &Color::UInt(0, 0, 0, 0), &[]).unwrap();
let rect = Rect::new(Point2D::new(0, 0), atlas_size);
rasterizer.draw_atlas(&image, &rect, &atlas, &outlines, &coverage_buffer).unwrap();
rasterizer.queue().flush().unwrap();
let draw_context = lord_drawquaad::Context::new();
let mut gl_texture = 0;
unsafe {
if instance.shading_language() == ShadingLanguage::Glsl {
gl::MemoryBarrier(gl::SHADER_IMAGE_ACCESS_BARRIER_BIT | gl::TEXTURE_FETCH_BARRIER_BIT);
}
gl::GenTextures(1, &mut gl_texture);
image.bind_to(&ExternalImage::GlTexture(gl_texture)).unwrap();
gl::BindTexture(gl::TEXTURE_RECTANGLE, gl_texture);
gl::TexParameteri(gl::TEXTURE_RECTANGLE, gl::TEXTURE_MIN_FILTER, gl::LINEAR as GLint);
gl::TexParameteri(gl::TEXTURE_RECTANGLE, gl::TEXTURE_MAG_FILTER, gl::LINEAR as GLint);
gl::TexParameteri(gl::TEXTURE_RECTANGLE, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as GLint);
gl::TexParameteri(gl::TEXTURE_RECTANGLE, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as GLint);
gl::Viewport(0, 0, device_pixel_width, device_pixel_height);
gl::ClearColor(1.0, 1.0, 1.0, 1.0);
gl::Clear(gl::COLOR_BUFFER_BIT);
}
draw_context.draw(gl_texture);
window.swap_buffers();
while !window.should_close() {
glfw.poll_events();
for (_, event) in glfw::flush_messages(&events) {
match event {
WindowEvent::Key(Key::Escape, _, Action::Press, _) => {
window.set_should_close(true)
}
_ => {}
}
}
}
}

View File

@ -1,983 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
extern crate clap;
extern crate compute_shader;
extern crate euclid;
extern crate gl;
extern crate glfw;
extern crate image;
extern crate memmap;
extern crate pathfinder;
use clap::{App, Arg};
use compute_shader::buffer;
use compute_shader::image::{Color, ExternalImage, Format, Image};
use compute_shader::instance::{Instance, ShadingLanguage};
use euclid::{Point2D, Rect, Size2D};
use gl::types::{GLchar, GLint, GLsizei, GLsizeiptr, GLuint, GLvoid};
use glfw::{Action, Context, Key, OpenGlProfileHint, SwapInterval, Window, WindowEvent};
use glfw::{Glfw, WindowHint, WindowMode};
use memmap::{Mmap, Protection};
use pathfinder::atlas::{AtlasBuilder, AtlasOptions, GlyphRasterizationOptions};
use pathfinder::charmap::CodepointRanges;
use pathfinder::coverage::{CoverageBuffer, CoverageBufferOptions};
use pathfinder::error::RasterError;
use pathfinder::font::Font;
use pathfinder::rasterizer::{DrawAtlasProfilingEvents, Rasterizer, RasterizerOptions};
use pathfinder::typesetter::{GlyphStore, PositionedGlyph, Typesetter};
use std::char;
use std::env;
use std::f32;
use std::fs::File;
use std::io::Read;
use std::mem;
use std::os::raw::c_void;
use std::path::{Path, PathBuf};
use std::sync::mpsc::Receiver;
const ATLAS_SIZE: u32 = 2048;
const WIDTH: u32 = 640;
const HEIGHT: u32 = 480;
const SCROLL_SPEED: f64 = 6.0;
const SUBPIXEL_GRANULARITY_COUNT: u8 = 4;
const SUBPIXEL_GRANULARITY: f32 = 1.0 / SUBPIXEL_GRANULARITY_COUNT as f32;
const INITIAL_POINT_SIZE: f32 = 24.0;
const MIN_POINT_SIZE: f32 = 6.0;
const MAX_POINT_SIZE: f32 = 512.0;
const FPS_DISPLAY_POINT_SIZE: f32 = 24.0;
const FPS_PADDING: i32 = 6;
static PATHFINDER_SHADER_PATH: &'static str = "resources/shaders/";
static EXAMPLE_SHADER_PATH: &'static str = "resources/examples/lorem-ipsum/";
static DEFAULT_TEXT_PATH: &'static str = "resources/examples/lorem-ipsum/default.txt";
static FPS_BACKGROUND_COLOR: [f32; 3] = [0.3, 0.3, 0.3];
static FPS_FOREGROUND_COLOR: [f32; 3] = [1.0, 1.0, 1.0];
static BACKGROUND_COLOR: [f32; 3] = [1.0, 1.0, 1.0];
static TEXT_COLOR: [f32; 3] = [0.0, 0.0, 0.0];
static ATLAS_DUMP_FILENAME: &'static str = "lorem-ipsum-atlas.png";
static RECT_INDICES: [u16; 6] = [0, 1, 3, 1, 2, 3];
fn main() {
DemoApp::new(AppOptions::parse()).run()
}
struct DemoApp {
options: AppOptions,
renderer: Renderer,
glfw: Glfw,
window: Window,
events: Receiver<(f64, WindowEvent)>,
point_size: f32,
translation: Point2D<i32>,
device_pixel_size: Size2D<u32>,
}
impl DemoApp {
fn new(options: AppOptions) -> DemoApp {
let mut glfw = glfw::init(glfw::LOG_ERRORS).unwrap();
glfw.window_hint(WindowHint::ContextVersion(3, 3));
glfw.window_hint(WindowHint::OpenGlForwardCompat(true));
glfw.window_hint(WindowHint::OpenGlProfile(OpenGlProfileHint::Core));
let context = glfw.create_window(WIDTH, HEIGHT, "lorem-ipsum", WindowMode::Windowed);
let (mut window, events) = context.expect("Couldn't create a window!");
window.make_current();
window.set_key_polling(true);
window.set_scroll_polling(true);
window.set_size_polling(true);
window.set_framebuffer_size_polling(true);
glfw.set_swap_interval(SwapInterval::Sync(1));
gl::load_with(|symbol| window.get_proc_address(symbol) as *const c_void);
let (width, height) = window.get_framebuffer_size();
let device_pixel_size = Size2D::new(width as u32, height as u32);
let renderer = Renderer::new(options.subpixel_aa);
DemoApp {
renderer: renderer,
glfw: glfw,
window: window,
events: events,
device_pixel_size: device_pixel_size,
point_size: INITIAL_POINT_SIZE,
translation: Point2D::zero(),
options: options,
}
}
fn run(&mut self) {
let file = Mmap::open_path(&self.options.font_path, Protection::Read).unwrap();
let mut buffer = vec![];
let font = unsafe {
Font::from_collection_index(file.as_slice(),
self.options.font_index,
&mut buffer).unwrap()
};
let page_width = self.device_pixel_size.width as f32 *
font.units_per_em() as f32 / INITIAL_POINT_SIZE;
let mut typesetter = Typesetter::new(page_width, &font, font.units_per_em() as f32);
typesetter.add_text(&font, font.units_per_em() as f32, &self.options.text);
let glyph_stores = GlyphStores::new(&typesetter, &font);
let mut dirty = true;
while !self.window.should_close() {
if dirty {
self.redraw(&glyph_stores, &typesetter, &font);
dirty = false
}
self.glfw.wait_events();
let events: Vec<_> = glfw::flush_messages(&self.events).map(|(_, e)| e).collect();
for event in events {
dirty = self.handle_window_event(event) || dirty
}
}
}
fn redraw(&mut self, glyph_stores: &GlyphStores, typesetter: &Typesetter, font: &Font) {
let redraw_result = self.renderer.redraw(self.point_size,
&font,
&glyph_stores.main,
typesetter,
&self.device_pixel_size,
&self.translation);
let (draw_time, accum_time);
match redraw_result.events {
Some(events) => {
let mut draw_nanos = 0u64;
unsafe {
gl::Flush();
gl::GetQueryObjectui64v(events.draw, gl::QUERY_RESULT, &mut draw_nanos);
}
draw_time = draw_nanos as f64;
accum_time = events.accum.time_elapsed().unwrap() as f64;
}
None => {
draw_time = 0.0;
accum_time = 0.0;
}
}
let timing = self.renderer.get_timing_in_ms();
self.renderer.draw_fps(&font,
&glyph_stores.fps,
&self.device_pixel_size,
draw_time,
accum_time,
timing,
redraw_result.glyphs_drawn);
self.window.swap_buffers();
}
// Returns true if the window needs to be redrawn.
fn handle_window_event(&mut self, event: WindowEvent) -> bool {
match event {
WindowEvent::Key(Key::Escape, _, Action::Press, _) => {
self.window.set_should_close(true);
false
}
WindowEvent::Key(Key::S, _, Action::Press, _) => {
self.renderer.take_screenshot();
println!("wrote screenshot to: {}", ATLAS_DUMP_FILENAME);
false
}
WindowEvent::Scroll(_, y) if self.window.get_key(Key::LeftAlt) == Action::Press ||
self.window.get_key(Key::RightAlt) == Action::Press => {
let old_point_size = self.point_size;
self.point_size = old_point_size + y as f32;
if self.point_size < MIN_POINT_SIZE {
self.point_size = MIN_POINT_SIZE
} else if self.point_size > MAX_POINT_SIZE {
self.point_size = MAX_POINT_SIZE
}
let offset = Point2D::new(self.device_pixel_size.width as f32,
self.device_pixel_size.height as f32);
let center = self.translation.cast().unwrap() - offset * 0.5;
self.translation = (center * self.point_size / old_point_size +
offset * 0.5).round().cast().unwrap();
true
}
WindowEvent::Scroll(x, y) => {
let vector = Point2D::new(x, y) * SCROLL_SPEED.round();
self.translation = self.translation + vector.cast().unwrap();
true
}
WindowEvent::Size(_, _) | WindowEvent::FramebufferSize(_, _) => {
let (width, height) = self.window.get_framebuffer_size();
self.device_pixel_size = Size2D::new(width as u32, height as u32);
true
}
_ => false,
}
}
}
struct AppOptions {
font_path: String,
font_index: u32,
text: String,
subpixel_aa: bool,
}
impl AppOptions {
fn parse() -> AppOptions {
let index_arg = Arg::with_name("index").short("i")
.long("index")
.help("Select an index within a font collection")
.takes_value(true);
let subpixel_antialiasing_arg =
Arg::with_name("subpixel-aa").short("s")
.long("subpixel-aa")
.help("Enable subpixel antialiasing");
let font_arg =
Arg::with_name("FONT-FILE").help("Select the font file (`.ttf`, `.otf`, etc.)")
.required(true)
.index(1);
let text_arg = Arg::with_name("TEXT-FILE").help("Select a file containing text to display")
.index(2);
let matches = App::new("lorem-ipsum").arg(index_arg)
.arg(subpixel_antialiasing_arg)
.arg(font_arg)
.arg(text_arg)
.get_matches();
let mut text = "".to_string();
let path = matches.value_of("TEXT-FILE").unwrap_or(DEFAULT_TEXT_PATH);
File::open(path).unwrap().read_to_string(&mut text).unwrap();
text = text.replace(&['\n', '\r', '\t'][..], " ");
let font_index = match matches.value_of("index") {
Some(index) => index.parse().unwrap(),
None => 0,
};
let font_path = matches.value_of("FONT-FILE").unwrap();
let subpixel_aa = matches.is_present("subpixel-aa");
AppOptions {
text: text,
font_index: font_index,
font_path: font_path.to_string(),
subpixel_aa: subpixel_aa,
}
}
}
struct GlyphStores {
main: GlyphStore,
fps: GlyphStore,
}
impl GlyphStores {
fn new(typesetter: &Typesetter, font: &Font) -> GlyphStores {
let main_glyph_store = typesetter.create_glyph_store(&font).unwrap();
let mut fps_chars: Vec<char> = vec![];
fps_chars.extend(" ./,:()".chars());
fps_chars.extend(('A' as u32..('Z' as u32 + 1)).flat_map(char::from_u32));
fps_chars.extend(('a' as u32..('z' as u32 + 1)).flat_map(char::from_u32));
fps_chars.extend(('0' as u32..('9' as u32 + 1)).flat_map(char::from_u32));
fps_chars.sort();
let fps_codepoint_ranges = CodepointRanges::from_sorted_chars(&fps_chars);
let fps_glyph_store = GlyphStore::from_codepoints(&fps_codepoint_ranges, &font).unwrap();
GlyphStores {
main: main_glyph_store,
fps: fps_glyph_store,
}
}
}
struct Renderer {
rasterizer: Rasterizer,
composite_program: GLuint,
composite_atlas_uniform: GLint,
composite_transform_uniform: GLint,
composite_translation_uniform: GLint,
composite_foreground_color_uniform: GLint,
composite_background_color_uniform: GLint,
main_composite_vertex_array: CompositeVertexArray,
fps_composite_vertex_array: CompositeVertexArray,
solid_color_program: GLuint,
solid_color_color_uniform: GLint,
solid_color_vertex_array: GLuint,
solid_color_vertex_buffer: GLuint,
solid_color_index_buffer: GLuint,
atlas_size: Size2D<u32>,
main_coverage_buffer: CoverageBuffer,
fps_coverage_buffer: CoverageBuffer,
main_compute_image: Image,
main_gl_texture: GLuint,
fps_compute_image: Image,
fps_gl_texture: GLuint,
query: GLuint,
shading_language: ShadingLanguage,
subpixel_aa: bool,
}
impl Renderer {
fn new(subpixel_aa: bool) -> Renderer {
let instance = Instance::new().unwrap();
let device = instance.open_device().unwrap();
let queue = device.create_queue().unwrap();
let mut rasterizer_options = RasterizerOptions::from_env().unwrap();
if env::var("PATHFINDER_SHADER_PATH").is_err() {
rasterizer_options.shader_path = PathBuf::from(PATHFINDER_SHADER_PATH)
}
let rasterizer = Rasterizer::new(&instance, device, queue, rasterizer_options).unwrap();
let (composite_program, composite_position_attribute, composite_tex_coord_attribute);
let (composite_atlas_uniform, composite_transform_uniform);
let (composite_translation_uniform, composite_foreground_color_uniform);
let composite_background_color_uniform;
let (main_composite_vertex_array, fps_composite_vertex_array);
let (solid_color_program, solid_color_position_attribute, solid_color_color_uniform);
let (mut solid_color_vertex_buffer, mut solid_color_index_buffer) = (0, 0);
let mut solid_color_vertex_array = 0;
unsafe {
composite_program = create_program("composite");
composite_position_attribute =
gl::GetAttribLocation(composite_program, "aPosition\0".as_ptr() as *const GLchar);
composite_tex_coord_attribute =
gl::GetAttribLocation(composite_program, "aTexCoord\0".as_ptr() as *const GLchar);
composite_atlas_uniform =
gl::GetUniformLocation(composite_program, "uAtlas\0".as_ptr() as *const GLchar);
composite_transform_uniform =
gl::GetUniformLocation(composite_program,
"uTransform\0".as_ptr() as *const GLchar);
composite_translation_uniform =
gl::GetUniformLocation(composite_program,
"uTranslation\0".as_ptr() as *const GLchar);
composite_foreground_color_uniform =
gl::GetUniformLocation(composite_program,
"uForegroundColor\0".as_ptr() as *const GLchar);
composite_background_color_uniform =
gl::GetUniformLocation(composite_program,
"uBackgroundColor\0".as_ptr() as *const GLchar);
solid_color_program = create_program("solid_color");
solid_color_position_attribute =
gl::GetAttribLocation(solid_color_program,
"aPosition\0".as_ptr() as *const GLchar);
solid_color_color_uniform =
gl::GetUniformLocation(solid_color_program, "uColor\0".as_ptr() as *const GLchar);
gl::UseProgram(composite_program);
main_composite_vertex_array = CompositeVertexArray::new();
fps_composite_vertex_array = CompositeVertexArray::new();
for vertex_array in &[&main_composite_vertex_array, &fps_composite_vertex_array] {
gl::BindVertexArray(vertex_array.vertex_array);
gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, vertex_array.index_buffer);
gl::BindBuffer(gl::ARRAY_BUFFER, vertex_array.vertex_buffer);
gl::VertexAttribPointer(composite_position_attribute as GLuint,
2,
gl::FLOAT,
gl::FALSE,
mem::size_of::<Vertex>() as GLsizei,
0 as *const GLvoid);
gl::VertexAttribPointer(composite_tex_coord_attribute as GLuint,
2,
gl::UNSIGNED_INT,
gl::FALSE,
mem::size_of::<Vertex>() as GLsizei,
(mem::size_of::<f32>() * 2) as *const GLvoid);
gl::EnableVertexAttribArray(composite_position_attribute as GLuint);
gl::EnableVertexAttribArray(composite_tex_coord_attribute as GLuint);
}
gl::UseProgram(solid_color_program);
gl::GenVertexArrays(1, &mut solid_color_vertex_array);
gl::BindVertexArray(solid_color_vertex_array);
gl::GenBuffers(1, &mut solid_color_vertex_buffer);
gl::GenBuffers(1, &mut solid_color_index_buffer);
gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, solid_color_index_buffer);
gl::BindBuffer(gl::ARRAY_BUFFER, solid_color_vertex_buffer);
gl::VertexAttribPointer(solid_color_position_attribute as GLuint,
2,
gl::FLOAT,
gl::FALSE,
mem::size_of::<i32>() as GLsizei * 2,
0 as *const GLvoid);
gl::EnableVertexAttribArray(solid_color_position_attribute as GLuint);
gl::BufferData(gl::ELEMENT_ARRAY_BUFFER,
(RECT_INDICES.len() * mem::size_of::<u32>()) as GLsizeiptr,
RECT_INDICES.as_ptr() as *const GLvoid,
gl::STATIC_DRAW);
}
// FIXME(pcwalton): Dynamically resizing atlas.
let atlas_size = Size2D::new(ATLAS_SIZE, ATLAS_SIZE);
let coverage_buffer_options = CoverageBufferOptions {
size: atlas_size,
subpixel_antialiasing: subpixel_aa,
..CoverageBufferOptions::default()
};
let main_coverage_buffer = CoverageBuffer::new(rasterizer.device(),
&coverage_buffer_options).unwrap();
let fps_coverage_buffer = CoverageBuffer::new(rasterizer.device(),
&coverage_buffer_options).unwrap();
let (main_compute_image, main_gl_texture) = create_image(&rasterizer, &atlas_size);
let (fps_compute_image, fps_gl_texture) = create_image(&rasterizer, &atlas_size);
let mut query = 0;
unsafe {
gl::GenQueries(1, &mut query);
}
let shading_language = instance.shading_language();
Renderer {
rasterizer: rasterizer,
composite_program: composite_program,
composite_atlas_uniform: composite_atlas_uniform,
composite_transform_uniform: composite_transform_uniform,
composite_translation_uniform: composite_translation_uniform,
composite_foreground_color_uniform: composite_foreground_color_uniform,
composite_background_color_uniform: composite_background_color_uniform,
main_composite_vertex_array: main_composite_vertex_array,
fps_composite_vertex_array: fps_composite_vertex_array,
solid_color_program: solid_color_program,
solid_color_color_uniform: solid_color_color_uniform,
solid_color_vertex_array: solid_color_vertex_array,
solid_color_vertex_buffer: solid_color_vertex_buffer,
solid_color_index_buffer: solid_color_index_buffer,
atlas_size: atlas_size,
main_coverage_buffer: main_coverage_buffer,
fps_coverage_buffer: fps_coverage_buffer,
main_compute_image: main_compute_image,
main_gl_texture: main_gl_texture,
fps_compute_image: fps_compute_image,
fps_gl_texture: fps_gl_texture,
query: query,
shading_language: shading_language,
subpixel_aa: subpixel_aa,
}
}
fn redraw(&self,
point_size: f32,
font: &Font,
glyph_store: &GlyphStore,
typesetter: &Typesetter,
device_pixel_size: &Size2D<u32>,
translation: &Point2D<i32>)
-> RedrawResult {
let shelf_height = font.shelf_height(point_size);
let atlas_options = AtlasOptions {
available_width: ATLAS_SIZE,
shelf_height: shelf_height,
subpixel_antialiasing: self.subpixel_aa,
..AtlasOptions::default()
};
let mut atlas_builder = AtlasBuilder::new(&atlas_options);
let (positioned_glyphs, cached_glyphs) = self.determine_visible_glyphs(&mut atlas_builder,
font,
glyph_store,
typesetter,
device_pixel_size,
translation,
point_size);
let atlas = atlas_builder.create_atlas().unwrap();
let rect = Rect::new(Point2D::new(0, 0), self.atlas_size);
let events = match self.rasterizer.draw_atlas(&self.main_compute_image,
&rect,
&atlas,
&glyph_store.outlines,
&self.main_coverage_buffer) {
Ok(events) => Some(events),
Err(RasterError::NoGlyphsToDraw) => None,
Err(error) => panic!("Failed to rasterize atlas: {:?}", error),
};
self.rasterizer.queue().flush().unwrap();
unsafe {
if self.shading_language == ShadingLanguage::Glsl {
gl::MemoryBarrier(gl::SHADER_IMAGE_ACCESS_BARRIER_BIT |
gl::TEXTURE_FETCH_BARRIER_BIT);
}
gl::Viewport(0,
0,
device_pixel_size.width as GLint,
device_pixel_size.height as GLint);
gl::ClearColor(BACKGROUND_COLOR[0], BACKGROUND_COLOR[1], BACKGROUND_COLOR[2], 1.0);
gl::Clear(gl::COLOR_BUFFER_BIT);
}
if events.is_some() {
self.draw_glyphs(glyph_store,
&self.main_composite_vertex_array,
&positioned_glyphs,
&cached_glyphs,
device_pixel_size,
translation,
self.main_gl_texture,
point_size,
&TEXT_COLOR,
&BACKGROUND_COLOR)
}
RedrawResult {
events: events,
glyphs_drawn: cached_glyphs.len() as u32,
}
}
fn determine_visible_glyphs(&self,
atlas_builder: &mut AtlasBuilder,
font: &Font,
glyph_store: &GlyphStore,
typesetter: &Typesetter,
device_pixel_size: &Size2D<u32>,
translation: &Point2D<i32>,
point_size: f32)
-> (Vec<PositionedGlyph>, Vec<CachedGlyph>) {
let viewport = Rect::new(-translation.cast().unwrap(), device_pixel_size.cast().unwrap());
let scale = point_size / font.units_per_em() as f32;
let positioned_glyphs = typesetter.positioned_glyphs_in_rect(&viewport,
glyph_store,
font.units_per_em() as f32,
scale,
SUBPIXEL_GRANULARITY);
let mut glyphs = vec![];
for positioned_glyph in &positioned_glyphs {
if positioned_glyph.glyph_index as usize >= glyphs.len() {
glyphs.resize(positioned_glyph.glyph_index as usize + 1, 0)
}
let subpixel = (positioned_glyph.subpixel_x / SUBPIXEL_GRANULARITY).round() as u8;
glyphs[positioned_glyph.glyph_index as usize] |= 1 << subpixel
}
let mut cached_glyphs = vec![];
for (glyph_index, &subpixels) in glyphs.iter().enumerate() {
for subpixel in 0..SUBPIXEL_GRANULARITY_COUNT {
if (subpixels & (1 << subpixel)) == 0 {
cached_glyphs.push(CachedGlyph(Point2D::zero()));
continue
}
let subpixel_offset = (subpixel as f32) / (SUBPIXEL_GRANULARITY as f32);
let options = GlyphRasterizationOptions {
point_size: point_size,
horizontal_offset: subpixel_offset,
..GlyphRasterizationOptions::default()
};
let origin = atlas_builder.pack_glyph(&glyph_store.outlines,
glyph_index as u16,
&options).unwrap();
cached_glyphs.push(CachedGlyph(origin));
}
}
(positioned_glyphs, cached_glyphs)
}
fn get_timing_in_ms(&self) -> f64 {
unsafe {
let mut result = 0;
gl::GetQueryObjectui64v(self.query, gl::QUERY_RESULT, &mut result);
(result as f64) / (1_000_000.0)
}
}
fn draw_glyphs(&self,
glyph_store: &GlyphStore,
vertex_array: &CompositeVertexArray,
positioned_glyphs: &[PositionedGlyph],
cached_glyphs: &[CachedGlyph],
device_pixel_size: &Size2D<u32>,
translation: &Point2D<i32>,
texture: GLuint,
point_size: f32,
foreground_color: &[f32],
background_color: &[f32]) {
unsafe {
gl::UseProgram(self.composite_program);
gl::BindVertexArray(vertex_array.vertex_array);
gl::BindBuffer(gl::ARRAY_BUFFER, vertex_array.vertex_buffer);
gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, vertex_array.index_buffer);
let vertex_count = self.upload_quads_for_text(glyph_store,
positioned_glyphs,
cached_glyphs,
point_size);
gl::ActiveTexture(gl::TEXTURE0);
gl::BindTexture(gl::TEXTURE_RECTANGLE, texture);
gl::Uniform1i(self.composite_atlas_uniform, 0);
let matrix = [
2.0 / device_pixel_size.width as f32, 0.0,
0.0, -2.0 / device_pixel_size.height as f32,
];
gl::UniformMatrix2fv(self.composite_transform_uniform, 1, gl::FALSE, matrix.as_ptr());
gl::Uniform2f(self.composite_translation_uniform,
-1.0 + 2.0 * translation.x as f32 / device_pixel_size.width as f32,
1.0 - 2.0 * translation.y as f32 / device_pixel_size.height as f32);
gl::Uniform3fv(self.composite_foreground_color_uniform, 1, foreground_color.as_ptr());
gl::Uniform3fv(self.composite_background_color_uniform, 1, background_color.as_ptr());
gl::Enable(gl::BLEND);
gl::BlendEquation(gl::FUNC_ADD);
gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
gl::BeginQuery(gl::TIME_ELAPSED, self.query);
gl::DrawElements(gl::TRIANGLES,
vertex_count as GLsizei,
gl::UNSIGNED_SHORT,
0 as *const GLvoid);
gl::EndQuery(gl::TIME_ELAPSED);
}
}
fn upload_quads_for_text(&self,
glyph_store: &GlyphStore,
positioned_glyphs: &[PositionedGlyph],
cached_glyphs: &[CachedGlyph],
point_size: f32)
-> usize {
let (mut vertices, mut indices) = (vec![], vec![]);
for positioned_glyph in positioned_glyphs {
let glyph_index = positioned_glyph.glyph_index;
let glyph_rect = glyph_store.outlines.glyph_subpixel_bounds(glyph_index, point_size);
let subpixel = (positioned_glyph.subpixel_x / SUBPIXEL_GRANULARITY).round() as u8;
let glyph_rect_i = glyph_rect.round_out();
let glyph_size_i = glyph_rect_i.size();
let cached_glyph_index = glyph_index as usize * SUBPIXEL_GRANULARITY_COUNT as usize +
subpixel as usize;
let cached_glyph = cached_glyphs[cached_glyph_index];
let uv_tl: Point2D<u32> = cached_glyph.0.floor().cast().unwrap();
let uv_br = uv_tl + glyph_size_i.cast().unwrap();
let left_pos = positioned_glyph.bounds.origin.x;
let top_pos = positioned_glyph.bounds.origin.y;
let right_pos = positioned_glyph.bounds.origin.x + glyph_size_i.width as f32;
let bottom_pos = positioned_glyph.bounds.origin.y + glyph_size_i.height as f32;
let first_index = vertices.len() as u16;
vertices.push(Vertex::new(left_pos, top_pos, uv_tl.x, uv_tl.y));
vertices.push(Vertex::new(right_pos, top_pos, uv_br.x, uv_tl.y));
vertices.push(Vertex::new(right_pos, bottom_pos, uv_br.x, uv_br.y));
vertices.push(Vertex::new(left_pos, bottom_pos, uv_tl.x, uv_br.y));
indices.extend(RECT_INDICES.iter().map(|index| first_index + index));
}
unsafe {
gl::BufferData(gl::ARRAY_BUFFER,
(vertices.len() * mem::size_of::<Vertex>()) as GLsizeiptr,
vertices.as_ptr() as *const GLvoid,
gl::STATIC_DRAW);
gl::BufferData(gl::ELEMENT_ARRAY_BUFFER,
(indices.len() * mem::size_of::<u16>()) as GLsizeiptr,
indices.as_ptr() as *const GLvoid,
gl::STATIC_DRAW);
}
indices.len()
}
fn draw_fps(&self,
font: &Font,
fps_glyph_store: &GlyphStore,
device_pixel_size: &Size2D<u32>,
draw_time: f64,
accum_time: f64,
composite_time: f64,
glyphs_drawn: u32) {
// Draw the background color.
unsafe {
gl::BindVertexArray(self.solid_color_vertex_array);
gl::UseProgram(self.solid_color_program);
gl::BindBuffer(gl::ARRAY_BUFFER, self.solid_color_vertex_buffer);
gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, self.solid_color_index_buffer);
let tl = Point2D::new(
-1.0,
-1.0 + (FPS_DISPLAY_POINT_SIZE + FPS_PADDING as f32 * 2.0) /
(device_pixel_size.height as f32) * 2.0);
let br = Point2D::new(1.0, -1.0);
let vertices = [(tl.x, tl.y), (br.x, tl.y), (br.x, br.y), (tl.x, br.y)];
gl::BufferData(gl::ARRAY_BUFFER,
(vertices.len() * mem::size_of::<(f32, f32)>()) as GLsizeiptr,
vertices.as_ptr() as *const GLvoid,
gl::DYNAMIC_DRAW);
gl::Uniform3fv(self.solid_color_color_uniform, 1, FPS_BACKGROUND_COLOR.as_ptr());
gl::Enable(gl::BLEND);
gl::BlendEquation(gl::FUNC_ADD);
gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
gl::DrawElements(gl::TRIANGLES, 6, gl::UNSIGNED_SHORT, 0 as *const GLvoid);
}
let fps_text = format!("draw: {:.3}ms ({:.3}us/glyph), \
accum: {:.3}ms ({:.3}us/glyph), \
composite: {:.3}ms ({:.3}us/glyph)",
draw_time / 1_000_000.0,
draw_time / (1000.0 * glyphs_drawn as f64),
accum_time / 1_000_000.0,
accum_time / (1000.0 * glyphs_drawn as f64),
composite_time,
(composite_time * 1000.0) / (glyphs_drawn as f64));
// TODO(pcwalton): Subpixel positioning for the FPS display.
let mut fps_typesetter = Typesetter::new(f32::INFINITY, &font, font.units_per_em() as f32);
fps_typesetter.add_text(&font, font.units_per_em() as f32, &fps_text);
let shelf_height = font.shelf_height(FPS_DISPLAY_POINT_SIZE);
let atlas_options = AtlasOptions {
available_width: ATLAS_SIZE,
shelf_height: shelf_height,
subpixel_antialiasing: self.subpixel_aa,
..AtlasOptions::default()
};
let mut fps_atlas_builder = AtlasBuilder::new(&atlas_options);
let (fps_positioned_glyphs, fps_cached_glyphs) =
self.determine_visible_glyphs(&mut fps_atlas_builder,
font,
fps_glyph_store,
&fps_typesetter,
device_pixel_size,
&Point2D::zero(),
FPS_DISPLAY_POINT_SIZE);
let fps_atlas = fps_atlas_builder.create_atlas().unwrap();
let rect = Rect::new(Point2D::new(0, 0), self.atlas_size);
self.rasterizer.draw_atlas(&self.fps_compute_image,
&rect,
&fps_atlas,
&fps_glyph_store.outlines,
&self.fps_coverage_buffer).unwrap();
self.rasterizer.queue().flush().unwrap();
let fps_pixels_per_unit = FPS_DISPLAY_POINT_SIZE / font.units_per_em() as f32;
let fps_line_spacing = ((font.ascender() as f32 - font.descender() as f32 +
font.line_gap() as f32) * fps_pixels_per_unit).round() as i32;
let fps_left = FPS_PADDING;
let fps_top = device_pixel_size.height as i32 - FPS_PADDING - fps_line_spacing;
self.draw_glyphs(&fps_glyph_store,
&self.fps_composite_vertex_array,
&fps_positioned_glyphs,
&fps_cached_glyphs,
device_pixel_size,
&Point2D::new(fps_left, fps_top),
self.fps_gl_texture,
FPS_DISPLAY_POINT_SIZE,
&FPS_FOREGROUND_COLOR,
&FPS_BACKGROUND_COLOR);
}
fn take_screenshot(&self) {
unsafe {
let mut fbo = 0;
gl::GenFramebuffers(1, &mut fbo);
gl::BindFramebuffer(gl::FRAMEBUFFER, fbo);
gl::FramebufferTexture2D(gl::FRAMEBUFFER,
gl::COLOR_ATTACHMENT0,
gl::TEXTURE_RECTANGLE,
self.main_gl_texture,
0);
let length = 4 * self.atlas_size.width as usize * self.atlas_size.height as usize;
let mut pixels: Vec<u8> = vec![0; length];
gl::ReadPixels(0, 0,
self.atlas_size.width as GLint, self.atlas_size.height as GLint,
gl::RGBA,
gl::UNSIGNED_BYTE,
pixels.as_mut_ptr() as *mut c_void);
gl::BindFramebuffer(gl::FRAMEBUFFER, 0);
gl::DeleteFramebuffers(1, &mut fbo);
image::save_buffer(&Path::new(ATLAS_DUMP_FILENAME),
&pixels,
self.atlas_size.width,
self.atlas_size.height,
image::RGBA(8)).unwrap();
}
}
}
#[derive(Clone, Copy, Debug)]
#[repr(C)]
struct Vertex {
x: f32,
y: f32,
u: u32,
v: u32,
}
impl Vertex {
fn new(x: f32, y: f32, u: u32, v: u32) -> Vertex {
Vertex {
x: x,
y: y,
u: u,
v: v,
}
}
}
#[derive(Clone, Copy, Debug)]
struct CachedGlyph(Point2D<f32>);
#[derive(Debug)]
struct CompositeVertexArray {
vertex_array: GLuint,
vertex_buffer: GLuint,
index_buffer: GLuint,
}
impl CompositeVertexArray {
fn new() -> CompositeVertexArray {
let (mut vertex_array, mut vertex_buffer, mut index_buffer) = (0, 0, 0);
unsafe {
gl::GenVertexArrays(1, &mut vertex_array);
gl::GenBuffers(1, &mut vertex_buffer);
gl::GenBuffers(1, &mut index_buffer);
}
CompositeVertexArray {
vertex_array: vertex_array,
vertex_buffer: vertex_buffer,
index_buffer: index_buffer,
}
}
}
struct RedrawResult {
events: Option<DrawAtlasProfilingEvents>,
glyphs_drawn: u32,
}
fn create_program(name: &str) -> GLuint {
unsafe {
let (mut vertex_shader_source, mut fragment_shader_source) = (vec![], vec![]);
File::open(&format!("{}/{}.vs.glsl",
EXAMPLE_SHADER_PATH,
name)).unwrap().read_to_end(&mut vertex_shader_source).unwrap();
File::open(&format!("{}/{}.fs.glsl",
EXAMPLE_SHADER_PATH,
name)).unwrap().read_to_end(&mut fragment_shader_source).unwrap();
let vertex_shader = gl::CreateShader(gl::VERTEX_SHADER);
let fragment_shader = gl::CreateShader(gl::FRAGMENT_SHADER);
gl::ShaderSource(vertex_shader,
1,
&(vertex_shader_source.as_ptr() as *const u8 as *const GLchar),
&(vertex_shader_source.len() as GLint));
gl::ShaderSource(fragment_shader,
1,
&(fragment_shader_source.as_ptr() as *const u8 as *const GLchar),
&(fragment_shader_source.len() as GLint));
gl::CompileShader(vertex_shader);
gl::CompileShader(fragment_shader);
let program = gl::CreateProgram();
gl::AttachShader(program, vertex_shader);
gl::AttachShader(program, fragment_shader);
gl::LinkProgram(program);
program
}
}
fn create_image(rasterizer: &Rasterizer, atlas_size: &Size2D<u32>) -> (Image, GLuint) {
let compute_image = rasterizer.device().create_image(Format::RGBA8,
buffer::Protection::ReadWrite,
&atlas_size).unwrap();
rasterizer.queue().submit_clear(&compute_image, &Color::UInt(0, 0, 0, 0), &[]).unwrap();
let mut gl_texture = 0;
unsafe {
gl::GenTextures(1, &mut gl_texture);
compute_image.bind_to(&ExternalImage::GlTexture(gl_texture)).unwrap();
gl::BindTexture(gl::TEXTURE_RECTANGLE, gl_texture);
gl::TexParameteri(gl::TEXTURE_RECTANGLE, gl::TEXTURE_MIN_FILTER, gl::LINEAR as GLint);
gl::TexParameteri(gl::TEXTURE_RECTANGLE, gl::TEXTURE_MAG_FILTER, gl::LINEAR as GLint);
gl::TexParameteri(gl::TEXTURE_RECTANGLE, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as GLint);
gl::TexParameteri(gl::TEXTURE_RECTANGLE, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as GLint);
}
(compute_image, gl_texture)
}

View File

@ -1,16 +0,0 @@
#version 330
uniform sampler2DRect uAtlas;
uniform vec3 uForegroundColor;
uniform vec3 uBackgroundColor;
in vec2 vTexCoord;
out vec4 oFragColor;
void main() {
vec3 value = texture(uAtlas, vTexCoord).rgb;
vec3 color = mix(uBackgroundColor, uForegroundColor, value);
oFragColor = vec4(color, 1.0f);
}

View File

@ -1,15 +0,0 @@
#version 330
uniform mat2 uTransform;
uniform vec2 uTranslation;
in vec2 aPosition;
in vec2 aTexCoord;
out vec2 vTexCoord;
void main() {
vTexCoord = aTexCoord;
gl_Position = vec4(uTransform * aPosition + uTranslation, 0.0f, 1.0f);
}

View File

@ -1,34 +0,0 @@
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur scelerisque pellentesque risus
quis vehicula. Ut sollicitudin aliquet diam, vel lobortis orci porta in. Sed eu nisi egestas odio
tincidunt cursus eget ut lorem. Fusce lacinia ex nec lectus rutrum mollis. Donec in ultrices
purus. Integer id suscipit magna. Suspendisse congue pulvinar neque id ultrices. Curabitur nec
tellus et est pellentesque posuere. Duis ut metus euismod, feugiat arcu vitae, posuere libero.
Curabitur nunc urna, rhoncus vitae scelerisque quis, viverra et odio. Suspendisse accumsan
pretium mi, nec fringilla metus condimentum id. Duis dignissim quam eu felis lobortis, eget
dignissim lectus fermentum. Nunc et massa id orci pellentesque rutrum. Nam imperdiet quam vel
ligula efficitur ultricies vel eu tellus. Maecenas luctus risus a erat euismod ultricies.
Pellentesque neque mauris, laoreet vitae finibus quis, molestie ut velit. Donec laoreet justo
risus. In id mi sed odio placerat interdum ut vitae erat. Fusce quis mollis mauris, sit amet
efficitur libero. In efficitur tortor nulla, sollicitudin sodales mi tempor in. In egestas
ultrices fermentum. Quisque mattis egestas nulla. Interdum et malesuada fames ac ante ipsum
primis in faucibus. Etiam in tempus sapien, in dignissim arcu. Quisque diam nulla, rhoncus et
tempor nec, facilisis porta purus. Nulla ut eros laoreet, placerat dolor ut, interdum orci. Sed
posuere eleifend mollis. Integer at nunc ex. Vestibulum aliquet risus quis lacinia convallis.
Fusce et metus viverra, varius nulla in, rutrum justo. Interdum et malesuada fames ac ante ipsum
primis in faucibus. Praesent non est vel lectus suscipit malesuada id ut nisl. Aenean sem ipsum,
tincidunt non orci non, varius consectetur purus. Aenean sed mollis turpis, sit amet vestibulum
risus. Nunc ut hendrerit urna, sit amet lacinia arcu. Curabitur laoreet a enim et eleifend. Etiam
consectetur pharetra massa, sed elementum quam molestie nec. Integer eu justo lectus. Vestibulum
sed vulputate sapien. Curabitur pretium luctus orci et interdum. Quisque ligula nisi, varius id
sodales id, volutpat et lorem. Pellentesque ex urna, malesuada at ex non, elementum ultricies
nulla. Nunc sodales, turpis at maximus bibendum, neque lorem laoreet felis, eget convallis sem
mauris ac quam. Mauris non pretium nulla. Nam semper pulvinar convallis. Suspendisse ultricies
odio vitae tortor congue, rutrum finibus nisl malesuada. Interdum et malesuada fames ac ante
ipsum primis in faucibus. Vestibulum aliquam et lacus sit amet lobortis. In sed ligula quis urna
accumsan vehicula sit amet id magna. Cras mollis orci vitae turpis porta, sed gravida nunc
aliquam. Phasellus nec facilisis nunc. Suspendisse volutpat leo felis, in iaculis nisi dignissim
et. Phasellus at urna purus. Nullam vitae metus ante. Praesent porttitor libero quis velit
fermentum rhoncus. Cras vitae rhoncus nulla. In efficitur risus sapien, sed viverra neque
scelerisque at. Morbi fringilla odio massa. Donec tincidunt magna diam, eget congue leo tristique
eget. Cras et sapien nulla.

View File

@ -1,10 +0,0 @@
#version 330
uniform vec3 uColor;
out vec4 oFragColor;
void main() {
oFragColor = vec4(uColor, 1.0f);
}

View File

@ -1,8 +0,0 @@
#version 330
in vec2 aPosition;
void main() {
gl_Position = vec4(aPosition, 0.0f, 1.0f);
}

View File

@ -1,43 +0,0 @@
// Copyright 2017 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// Computes total coverage and writes into the output atlas.
//
// This proceeds top to bottom for better data locality. For details on the algorithm, see [1].
//
// [1]: https://medium.com/@raphlinus/inside-the-fastest-font-renderer-in-the-world-75ae5270c445
layout(local_size_x = 1024) in;
layout(IMAGE_FORMAT, binding = 0) uniform restrict writeonly image2DRect uImage;
layout(r32f, binding = 1) uniform restrict readonly image2DRect uCoverage;
layout(location = 2) uniform uvec4 uAtlasRect;
layout(location = 3) uniform uint uAtlasShelfHeight;
void main() {
// Determine the boundaries of the column we'll be traversing.
uint atlasWidth = uAtlasRect.z - uAtlasRect.x;
uint column = gl_GlobalInvocationID.x % atlasWidth;
uint shelfIndex = gl_GlobalInvocationID.x / atlasWidth;
uint firstRow = shelfIndex * uAtlasShelfHeight;
uint lastRow = (shelfIndex + 1u) * uAtlasShelfHeight;
uint atlasHeight = uAtlasRect.w - uAtlasRect.y;
if (firstRow >= atlasHeight)
return;
// Sweep down the column, accumulating coverage as we go.
float coverage = 0.0f;
for (uint row = firstRow; row < lastRow; row++) {
ivec2 coord = ivec2(column, row);
coverage += imageLoad(uCoverage, coord).r;
imageStore(uImage, coord + ivec2(uAtlasRect.xy), vec4(coverage, coverage, coverage, 1.0));
}
}

View File

@ -1,26 +0,0 @@
// Copyright 2017 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// Common functions for all accumulation operations.
// Determines the boundaries of the column we'll be traversing.
void get_location(__private uint *lColumn,
__private uint *lFirstRow,
__private uint *lLastRow,
uint4 kAtlasRect,
uint kAtlasShelfHeight) {
uint atlasWidth = kAtlasRect.z - kAtlasRect.x, atlasHeight = kAtlasRect.w - kAtlasRect.y;
uint shelfIndex = get_global_id(0) / atlasWidth;
*lColumn = get_global_id(0) % atlasWidth;
*lFirstRow = min(shelfIndex * kAtlasShelfHeight, atlasHeight);
*lLastRow = min((shelfIndex + 1) * kAtlasShelfHeight, atlasHeight);
}

View File

@ -1,14 +0,0 @@
// Copyright 2017 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// Placeholder file.
//
// TODO(pcwalton): Fill this in.

View File

@ -1,37 +0,0 @@
// Copyright 2017 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// Computes total coverage and writes into the output atlas for grayscale antialiasing.
//
// This proceeds top to bottom for better data locality. For details on the algorithm, see [1].
//
// [1]: https://medium.com/@raphlinus/inside-the-fastest-font-renderer-in-the-world-75ae5270c445
const sampler_t SAMPLER = CLK_NORMALIZED_COORDS_FALSE | CLK_ADDRESS_NONE | CLK_FILTER_NEAREST;
__kernel void accum_gray(__write_only image2d_t gImage,
__read_only image2d_t gCoverage,
uint4 kAtlasRect,
uint kAtlasShelfHeight) {
// Determine the boundaries of the column we'll be traversing.
uint column = 0, firstRow = 0, lastRow = 0;
get_location(&column, &firstRow, &lastRow, kAtlasRect, kAtlasShelfHeight);
// Sweep down the column, accumulating coverage as we go.
float coverage = 0.0f;
for (uint row = firstRow; row < lastRow; row++) {
int2 coord = (int2)((int)column, (int)row);
coverage += read_imagef(gCoverage, SAMPLER, coord).r;
float gray = fabs(coverage);
write_imagef(gImage, coord + (int2)kAtlasRect.xy, (float4)(gray, gray, gray, 1.0f));
}
}

View File

@ -1,40 +0,0 @@
// Copyright 2017 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// Computes total coverage and writes into the output atlas for subpixel antialiasing.
//
// This proceeds top to bottom for better data locality. For details on the algorithm, see [1].
//
// [1]: https://medium.com/@raphlinus/inside-the-fastest-font-renderer-in-the-world-75ae5270c445
const sampler_t SAMPLER = CLK_NORMALIZED_COORDS_FALSE | CLK_ADDRESS_NONE | CLK_FILTER_NEAREST;
__kernel void accum_subpixel(__write_only image2d_t gImage,
__read_only image2d_t gCoverage,
uint4 kAtlasRect,
uint kAtlasShelfHeight) {
// Determine the boundaries of the column we'll be traversing.
uint column = 0, firstRow = 0, lastRow = 0;
get_location(&column, &firstRow, &lastRow, kAtlasRect, kAtlasShelfHeight);
// Sweep down the column, accumulating coverage as we go.
float3 coverage = (float3)(0.0f, 0.0f, 0.0f);
for (uint row = firstRow; row < lastRow; row++) {
int coverageColumn = (int)(column * 3);
coverage.r += read_imagef(gCoverage, SAMPLER, (int2)(coverageColumn, (int)row)).r;
coverage.g += read_imagef(gCoverage, SAMPLER, (int2)(coverageColumn + 1, (int)row)).r;
coverage.b += read_imagef(gCoverage, SAMPLER, (int2)(coverageColumn + 2, (int)row)).r;
int2 coord = (int2)((int)column, (int)row) + (int2)kAtlasRect.xy;
float3 aa = fabs(coverage);
write_imagef(gImage, coord, (float4)(aa, 1.0f));
}
}

View File

@ -1,117 +0,0 @@
// Copyright 2017 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#version 330
// The size of the atlas in pixels.
uniform uvec2 uAtlasSize;
// The starting point of the segment.
flat in vec2 vP0;
// The endpoint of this segment.
flat in vec2 vP1;
// 1.0 if this segment runs left to right; -1.0 otherwise.
flat in float vDirection;
// The slope of this line.
flat in float vSlope;
// Minimum and maximum vertical extents, unrounded.
flat in vec2 vYMinMax;
out vec4 oFragColor;
void main() {
// Compute the X boundaries of this pixel.
float xMin = floor(gl_FragCoord.x);
float xMax = xMin + 1.0f;
// Compute the horizontal span that the line segment covers across this pixel.
float dX = min(xMax, vP1.x) - max(xMin, vP0.x);
// Compute the Y-intercepts of the portion of the line crossing this pixel.
float yMin = clamp(vP0.y + (xMin - vP0.x) * vSlope, vYMinMax.x, vYMinMax.y);
float yMax = clamp(yMin + vSlope, vYMinMax.x, vYMinMax.y);
if (yMin > yMax) {
float tmp = yMin;
yMin = yMax;
yMax = tmp;
}
// Round the Y-intercepts out to the nearest pixel.
int yMinI = int(floor(yMin)), yMaxI = int(ceil(yMax));
// Determine which vertical pixel we're looking at.
int yI = int(floor(gl_FragCoord.y));
// Compute trapezoidal area coverage.
//
// It may be helpful to follow along with this explanation, keeping in mind that we compute
// downward coverage rather than rightward coverage:
//
// http://nothings.org/gamedev/rasterize/
//
// Note that the algorithm above computes total area coverage for each pixel, while here we
// compute *delta* coverage: that is, the *difference* in the area covered between this pixel
// and the pixel above it. In general, this means that, in contrast to the stb_truetype
// algorithm, we have to specially handle the first fully covered pixel, in order to account
// for the residual area difference between that pixel and the one above it.
float coverage = 0.0f;
if (yMaxI <= yMinI + 1) {
// The line touches only one pixel (case 1). Compute the area of that trapezoid (or the
// residual area for the pixel right after that trapezoid).
float trapArea = 0.5f * (yMin + yMax) - float(yMinI);
if (yI == yMinI)
coverage = 1.0f - trapArea;
else if (yI == yMinI + 1)
coverage = trapArea;
} else {
// The line touches multiple pixels (case 2). There are several subcases to handle here.
// Compute the area of the topmost triangle.
float yMinF = fract(yMin);
float dXdY = 1.0f / (yMax - yMin);
float triAreaMin = 0.5f * dXdY * (1.0f - yMinF) * (1.0f - yMinF);
if (yI == yMinI) {
// We're looking at the pixel that triangle covers, so we're done.
coverage = triAreaMin;
} else {
// Compute the area of the bottommost triangle.
float yMaxF = yMax - ceil(yMax) + 1.0f;
float triAreaMax = 0.5f * dXdY * yMaxF * yMaxF;
bool lineTouchesThreePixels = yMaxI == yMinI + 2;
if (lineTouchesThreePixels && yI == yMinI + 1) {
// The line touches exactly 3 pixels, and we're looking at the middle one.
coverage = 1.0f - triAreaMin - triAreaMax;
} else if (!lineTouchesThreePixels && yI < yMaxI) {
// The line touches more than 3 pixels. Compute the area of the first trapezoid.
float trapAreaMin = dXdY * (1.5f - yMinF);
if (yI == yMinI + 1) {
// We're looking at that trapezoid, so we're done.
coverage = trapAreaMin - triAreaMin;
} else if (yI == yMaxI - 1) {
// We're looking at the last trapezoid. Compute its area.
float trapAreaMax = trapAreaMin + float(yMaxI - yMinI - 3) * dXdY;
coverage = 1.0f - trapAreaMax - triAreaMax;
} else if (yI > yMinI + 1 && yI < yMaxI - 1) {
// We're looking at one of the pixels in between the two trapezoids. The delta
// coverage in this case is simply the inverse slope.
coverage = dXdY;
}
} else if (yI == yMaxI) {
// We're looking at the final pixel in the column.
coverage = triAreaMax;
}
}
}
oFragColor = vec4(dX * vDirection * coverage, 1.0f, 1.0f, 1.0f);
}

View File

@ -1,135 +0,0 @@
// Copyright 2017 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// A geometry shader fallback when tessellation is not available. This is *not* linked into the
// program by default.
//
// This will probably not perform well, but it's useful for testing, since llvmpipe does not
// support tessellation as of Jan. 2017.
//
// To use this shader, set `RasterizerOptions::force_geometry_shader` to true or set the
// environment variable `PATHFINDER_FORCE_GEOMETRY_SHADER` to 1.
#version 330
#define CURVE_THRESHOLD 0.333f
#define CURVE_TOLERANCE 3.0f
#define PIXELS_TO_DEVICE(x, y) (vec2((x), (y)) / vec2(uAtlasSize) * 2.0f - 1.0f)
#define SET_VARYINGS(primID) \
vP0 = lP0; \
vP1 = lP1; \
vDirection = direction; \
vSlope = slope; \
vYMinMax = yMinMax; \
gl_PrimitiveID = gl_PrimitiveIDIn + (primID)
layout(triangles) in;
layout(triangle_strip, max_vertices = 256) out;
// The size of the atlas in pixels.
uniform uvec2 uAtlasSize;
// The vertex ID, passed into this shader.
flat in int vVertexID[];
// The starting point of the segment.
flat out vec2 vP0;
// The endpoint of this segment.
flat out vec2 vP1;
// 1.0 if this segment runs left to right; -1.0 otherwise.
flat out float vDirection;
// The slope of this line.
flat out float vSlope;
// Minimum and maximum vertical extents, unrounded.
flat out vec2 vYMinMax;
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;
// Compute direction. Flip around if necessary so that p0 is to the left of p2.
float direction;
if (p0.x < p2.x) {
direction = 1.0f;
} else {
direction = -1.0f;
vec2 tmp = p0;
p0 = p2;
p2 = tmp;
}
// Determine how many lines to divide into.
uint lineCount = 1u;
if (vVertexID[1] > 0) {
// Quadratic curve.
vec2 dev = p0 - 2.0f * p1 + p2;
float devSq = dot(dev, dev);
if (devSq >= CURVE_THRESHOLD) {
// Inverse square root is likely no slower and may be faster than regular square root
// (e.g. on x86).
lineCount += uint(floor(inversesqrt(inversesqrt(CURVE_TOLERANCE * devSq))));
}
}
// Divide into lines.
for (uint lineIndex = 0u; lineIndex < lineCount; lineIndex++) {
// Compute our endpoints (trivial if this is part of a line, less trivial if this is part
// of a curve).
vec2 lP0, lP1;
if (lineCount == 1u) {
lP0 = p0;
lP1 = p2;
} else {
float t0 = float(lineIndex + 0u) / float(lineCount);
float t1 = float(lineIndex + 1u) / float(lineCount);
lP0 = mix(mix(p0, p1, t0), mix(p1, p2, t0), t0);
lP1 = mix(mix(p0, p1, t1), mix(p1, p2, t1), t1);
}
// Compute Y extents and slope.
vec2 yMinMax = lP0.y <= lP1.y ? vec2(lP0.y, lP1.y) : vec2(lP1.y, lP0.y);
float slope = (lP1.y - lP0.y) / (lP1.x - lP0.x);
// Convert atlas space to device space.
vec2 pTL = PIXELS_TO_DEVICE(floor(lP0.x), floor(yMinMax.x));
vec2 pBR = PIXELS_TO_DEVICE(ceil(lP1.x), ceil(yMinMax.y) + 1.0f);
vec2 pTR = vec2(pBR.x, pTL.y);
vec2 pBL = vec2(pTL.x, pBR.y);
// Assign primitive IDs.
int primID0 = int(lineIndex) * 2, primID1 = int(lineIndex) * 2 + 1;
// Emit vertices.
SET_VARYINGS(primID0);
gl_Position = vec4(pTL, 0.0f, 1.0f);
EmitVertex();
SET_VARYINGS(primID0);
gl_Position = vec4(pTR, 0.0f, 1.0f);
EmitVertex();
SET_VARYINGS(primID0);
gl_Position = vec4(pBL, 0.0f, 1.0f);
EmitVertex();
EndPrimitive();
SET_VARYINGS(primID1);
gl_Position = vec4(pBR, 0.0f, 1.0f);
EmitVertex();
SET_VARYINGS(primID1);
gl_Position = vec4(pBL, 0.0f, 1.0f);
EmitVertex();
SET_VARYINGS(primID1);
gl_Position = vec4(pTR, 0.0f, 1.0f);
EmitVertex();
EndPrimitive();
}
}

View File

@ -1,118 +0,0 @@
// Copyright 2017 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#version 410
#define CURVE_THRESHOLD 0.333f
#define CURVE_TOLERANCE 3.0f
layout(vertices = 1) out;
// The vertex ID, passed into this shader.
flat in int vVertexID[];
// These outputs really should be patch outs, but that causes problems in Apple drivers.
// The starting point of the segment.
out vec2 vpP0[];
// The first control point, if this is a curve. If this is a line, this value must be ignored.
out vec2 vpP1[];
// 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
// order to work around an Apple bug in the Radeon driver.
out float vpTessLevel[];
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) {
// 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
// (e.g. on x86).
lineCount += floor(inversesqrt(inversesqrt(CURVE_TOLERANCE * devSq)));
}
}
// Tessellate into lines. This is subtle, so a diagram may help.
//
// Suppose we decided to divide this curve up into 4 lines. Then our abstract tessellated patch
// space will look like this:
//
// x₀ x₁ x₂ x₃ x₄ x₅ x₆ x₇
// ┌──┬──┬──┬──┬──┬──┬──┐
// │▒▒│ │▒▒│ │▒▒│ │▒▒│
// │▒▒│ │▒▒│ │▒▒│ │▒▒│
// └──┴──┴──┴──┴──┴──┴──┘
//
// The shaded areas are the only areas that will actually be drawn. They might look like this:
//
// x₅
// x₆ x₇
// x₃ ┌───────┐
// x₄ │▒▒▒▒▒▒▒│
// x₁ ┌─────┼───────┘
// x₂ │▒▒▒▒▒│
// ┌──┼─────┘
// │▒▒│
// │▒▒│
// x₀ │▒▒│
// ┌──┼──┘
// │▒▒│
// │▒▒│
// └──┘
//
// In this way, the unshaded areas become zero-size and are discarded by the rasterizer.
//
// Note that, in reality, it will often be the case that the quads overlap vertically by one
// pixel in the horizontal direction. In fact, this will occur whenever a line segment endpoint
// does not precisely straddle a pixel boundary. However, observe that we can guarantee that
// x₂ ≤ x₁, x₄ ≤ x₃, and so on, because there is never any horizontal space between endpoints.
// This means that all triangles inside the unshaded areas are guaranteed to be wound in the
// opposite direction from those inside the shaded areas. Because the OpenGL spec guarantees
// that, by default, all tessellated triangles are wound counterclockwise in abstract patch
// space, the triangles within the unshaded areas must be wound clockwise and are therefore
// candidates for backface culling. Backface culling is always enabled when running Pathfinder,
// 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 == 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;
gl_TessLevelOuter[1] = tessLevel;
gl_TessLevelOuter[2] = 1.0f;
gl_TessLevelOuter[3] = tessLevel;
// NB: These per-patch outputs must be assigned in this order, or Apple's compiler will
// miscompile us.
vpP0[gl_InvocationID] = p0;
vpP1[gl_InvocationID] = p1;
vpP2[gl_InvocationID] = p2;
vpP3[gl_InvocationID] = p3;
vpTessLevel[gl_InvocationID] = tessLevel;
}

View File

@ -1,108 +0,0 @@
// Copyright 2017 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#version 410
layout(quads) in;
// The size of the atlas in pixels.
uniform uvec2 uAtlasSize;
// The starting point of the segment.
in vec2 vpP0[];
// The first control point, if this is a curve. If this is a line, this value must be ignored.
in vec2 vpP1[];
// 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
// order to work around an Apple bug in the Radeon driver.
in float vpTessLevel[];
// The starting point of the segment.
flat out vec2 vP0;
// The endpoint of this segment.
flat out vec2 vP1;
// 1.0 if this segment runs left to right; -1.0 otherwise.
flat out float vDirection;
// The slope of this line.
flat out float vSlope;
// Minimum and maximum vertical extents, unrounded.
flat out vec2 vYMinMax;
void main() {
// Read in curve points.
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.
uint tessPointCount = uint(vpTessLevel[0] + 1.0f);
uint tessIndex = uint(round(gl_TessCoord.x * float(tessPointCount - 1)));
uint lineCount = tessPointCount / 2, lineIndex = tessIndex / 2, endpoint = tessIndex % 2;
// Compute our endpoints (trivial if this is part of a line, less trivial if this is part of a
// curve).
vec2 p0, p1;
if (lineCount == 1) {
p0 = cP0;
p1 = cP3;
} else {
float t0 = float(lineIndex + 0) / float(lineCount);
float t1 = float(lineIndex + 1) / float(lineCount);
// 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
// right if necessary.
float direction;
if (p0.x < p1.x) {
direction = 1.0f;
} else {
direction = -1.0f;
vec2 tmp = p0;
p0 = p1;
p1 = tmp;
}
// Forward points and direction onto the fragment shader.
vP0 = p0;
vP1 = p1;
vDirection = direction;
// Compute Y extents and slope.
vSlope = (p1.y - p0.y) / (p1.x - p0.x);
vYMinMax = p0.y <= p1.y ? vec2(p0.y, p1.y) : vec2(p1.y, p0.y);
// Compute our final position in atlas space, rounded out to the next pixel.
float x = endpoint == 0 ? floor(p0.x) : ceil(p1.x);
float y = gl_TessCoord.y == 0.0f ? floor(vYMinMax.x) : ceil(vYMinMax.y) + 1.0f;
// Convert atlas space to device space.
gl_Position = vec4(vec2(x, y) / vec2(uAtlasSize) * 2.0f - 1.0f, 0.0f, 1.0f);
}

View File

@ -1,66 +0,0 @@
// Copyright 2017 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#version 330
#define MAX_GLYPHS 2048
// Accessors to work around Apple driver bugs.
#define GLYPH_DESCRIPTOR_UNITS_PER_EM(d) (d).misc.x
#define IMAGE_DESCRIPTOR_ATLAS_POS(d) (d).xy
#define IMAGE_DESCRIPTOR_POINT_SIZE(d) (d).z
// Information about the metrics of each glyph.
struct GlyphDescriptor {
// The left/bottom/right/top offsets of the glyph from point (0, 0) in glyph space.
ivec4 extents;
// x: Units per em.
uvec4 misc;
};
// The size of the atlas in pixels.
uniform uvec2 uAtlasSize;
// Whether subpixel antialiasing is in use.
uniform bool uSubpixelAA;
layout(std140) uniform ubGlyphDescriptors {
GlyphDescriptor uGlyphs[MAX_GLYPHS];
};
layout(std140) uniform ubImageDescriptors {
vec4 uImages[MAX_GLYPHS];
};
// The position of each vertex in glyph space.
in ivec2 aPosition;
// Which glyph the vertex belongs to.
in uint aGlyphIndex;
// The vertex ID, passed along onto the TCS.
flat out int vVertexID;
void main() {
vVertexID = gl_VertexID;
vec4 image = uImages[aGlyphIndex];
GlyphDescriptor glyph = uGlyphs[aGlyphIndex];
vec2 glyphPos = vec2(aPosition.x - glyph.extents.x, glyph.extents.w - aPosition.y);
float pointSize = IMAGE_DESCRIPTOR_POINT_SIZE(image);
vec2 glyphPxPos = glyphPos * pointSize / GLYPH_DESCRIPTOR_UNITS_PER_EM(glyph);
vec2 atlasPos = glyphPxPos + IMAGE_DESCRIPTOR_ATLAS_POS(image);
if (uSubpixelAA)
atlasPos.x *= 3.0f;
gl_Position = vec4(atlasPos, 0.0f, 1.0f);
}

View File

@ -1,17 +0,0 @@
// Copyright 2017 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#version 330
#extension GL_ARB_compute_shader : require
#extension GL_ARB_explicit_uniform_location : require
#extension GL_ARB_shader_image_load_store : require
#extension GL_ARB_shader_storage_buffer_object : require
#extension GL_ARB_shading_language_420pack : require

View File

@ -1,340 +0,0 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Library General
Public License instead of this License.

View File

@ -1,51 +0,0 @@
Cyrillized free URW fonts.
These fonts were made from the free URW fonts distributed with ghostcript.
There are NO changes in the latin part of them (I hope).
Cyrillic glyphs were added by copying suitable latin ones
and painting oulines of unique cyrillic glyphs in same style as the others.
For all modification pfaedit was used.
The license for result is (of course) same as for original fonts,
i.e. GPL with an exception that you can put these fonts in your own non-GPLed
documents. (Looks like LGPL from my point of view =).
The "sources" of these fonts in the native pfaedit format are available
at ftp://ftp.gnome.ru/fonts/sources
The great font editor pfaedit is available at http://pfaedit.sf.net.
That page also includes some links to fonts created by
George Williams -- the author of pfaedit.
Acknowledgements:
I would like to thank George Williams, the pfaedit's author and developer.
He is the most bug-reporter/feature-requester friendly developer
I ever saw in my not so short life. At some moment in the future
I must write a book about him: "George Williams and my best experience
in bug-reporting." George also greatly helped me bug-hunting these fonts,
explained to me some very important things about fonts and font design,
quickly adopted pfaedit to my needs (or pointed me to The Right Place in
documentation where I found better way of doing things).
I would like to thank Alexey Novodvorsky (aka AEN), who
pointed me to pfaedit and George Williams' fonts, explained
The Task to me. He is also one of the main participators in the
development of Sysiphus -- free repository of free software.
I didn't loose my time for compiling/installing and supporting
my linux box coz I used the result of Sysiphus developers' works.
I would like to thank Sergey Vlasov, who tested these fonts and reported
about bugs. Also he help me to make some bug-reports to George about
pfaedit bugs.
I would like Dmitry 40in, who did big QA for some font outlines, drawn some glyphs,
and explain some The Truths for me.
I would like to thank Vlad Harchev (aka hvv), who
proofread this text for me.
Also I have to thank RMS for GPL and URW for releasing the fonts
under it.
Thank you very much!
Valek Filippov frob@df.ru
(C)opyLeft 2001

View File

@ -1,358 +0,0 @@
// Copyright 2017 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! Atlases, which hold rendered glyphs on the GPU.
use error::GlError;
use euclid::Point2D;
use gl::types::{GLenum, GLsizei, GLsizeiptr, GLuint, GLvoid};
use gl;
use outline::Outlines;
use rect_packer::RectPacker;
use std::mem;
use std::os::raw::c_void;
use std::u16;
/// Places glyphs in an atlas.
///
/// Atlases are composed of vertically-stacked "shelves" of uniform height. No glyphs may cross
/// shelves. Therefore, the shelf height must be tall enough to encompass all of the glyphs you
/// wish to render into the atlas.
///
/// Typically, when using Pathfinder, you first create an atlas builder, place all the glyphs into
/// it, generate the atlas, and then pass that glyph to a rasterizer for rendering on the GPU.
/// Afterward, you can retrieve the positions of each glyph in the atlas for final composition to
/// the screen.
pub struct AtlasBuilder {
rect_packer: RectPacker,
batch_builders: Vec<BatchBuilder>,
options: AtlasOptions,
}
impl AtlasBuilder {
/// Constructs a new atlas builder with the given options.
///
/// At least `available_width` and `shelf_height` must be set in the options.
///
/// The width can be any value at least as large as all glyphs in the font. It is recommended
/// to keep it fairly large in order to make efficient use of the space: 1024 or 2048 is a good
/// choice on modern GPUs.
///
/// The shelf height should be the maximum of all minimum shelf heights for all fonts you wish
/// to render into the atlas. You can retrive the minimum shelf height for a font with the
/// `Font::shelf_height()` method.
///
/// Remember to set the `subpixel_antialiasing` field to true if you're using subpixel AA.
#[inline]
pub fn new(options: &AtlasOptions) -> AtlasBuilder {
AtlasBuilder {
rect_packer: RectPacker::new(options.available_width, options.shelf_height),
batch_builders: vec![],
options: *options,
}
}
/// Places a glyph into the atlas.
///
/// The glyph is supplied as an *index* into the supplied outline buffer. Note that indices are
/// separate from IDs; the indices are returned from each call to
/// `OutlineBuilder::add_glyph()`.
///
/// Returns the subpixel origin of the glyph in the atlas if successful or an error if there is
/// no space left for the glyph.
pub fn pack_glyph(&mut self,
outlines: &Outlines,
glyph_index: u16,
options: &GlyphRasterizationOptions)
-> Result<Point2D<f32>, ()> {
let mut subpixel_bounds = outlines.glyph_subpixel_bounds(glyph_index, options.point_size);
subpixel_bounds.left += options.horizontal_offset;
subpixel_bounds.right += options.horizontal_offset;
let pixel_bounds = subpixel_bounds.round_out();
let atlas_origin = try!(self.rect_packer.pack(&pixel_bounds.size().cast().unwrap()));
for batch_builder in &mut self.batch_builders {
if let Ok(atlas_origin) = batch_builder.add_glyph(outlines,
&atlas_origin,
glyph_index,
options) {
return Ok(atlas_origin)
}
}
let mut batch_builder = BatchBuilder::new();
let atlas_origin = try!(batch_builder.add_glyph(outlines,
&atlas_origin,
glyph_index,
options));
self.batch_builders.push(batch_builder);
Ok(atlas_origin)
}
/// Creates an atlas by uploading the atlas info to the GPU.
pub fn create_atlas(self) -> Result<Atlas, GlError> {
let mut batches = vec![];
for batch_builder in self.batch_builders.into_iter() {
batches.push(try!(batch_builder.create_batch()))
}
Ok(Atlas {
batches: batches,
shelf_height: self.rect_packer.shelf_height(),
shelf_columns: self.rect_packer.shelf_columns(),
options: self.options,
})
}
}
struct BatchBuilder {
image_descriptors: Vec<ImageDescriptor>,
image_metadata: Vec<ImageMetadata>,
}
impl BatchBuilder {
fn new() -> BatchBuilder {
BatchBuilder {
image_descriptors: vec![],
image_metadata: vec![],
}
}
fn add_glyph(&mut self,
outlines: &Outlines,
atlas_origin: &Point2D<u32>,
glyph_index: u16,
options: &GlyphRasterizationOptions)
-> Result<Point2D<f32>, ()> {
// Check to see if we're already rendering this glyph.
let image_index = glyph_index as usize;
if let Some(image_descriptor) = self.image_descriptors.get(image_index) {
if image_descriptor.point_size == options.point_size &&
self.image_metadata[image_index]
.horizontal_offset == options.horizontal_offset {
// Glyph is already present.
return Ok(Point2D::new(image_descriptor.atlas_x, image_descriptor.atlas_y))
} else {
// Glyph is present at a different font size. We need a new batch.
return Err(())
}
}
let subpixel_bounds = outlines.glyph_subpixel_bounds(glyph_index, options.point_size);
let glyph_id = outlines.glyph_id(glyph_index);
while self.image_descriptors.len() < image_index + 1 {
self.image_descriptors.push(ImageDescriptor::default());
self.image_metadata.push(ImageMetadata::default());
}
let units_per_em = outlines.glyph_units_per_em(glyph_index) as f32;
let horizontal_px_offset = options.horizontal_offset / units_per_em * options.point_size;
let atlas_origin = Point2D::new(
atlas_origin.x as f32 + subpixel_bounds.left.fract() + horizontal_px_offset,
atlas_origin.y as f32 + 1.0 - subpixel_bounds.top.fract());
self.image_descriptors[image_index] = ImageDescriptor {
atlas_x: atlas_origin.x,
atlas_y: atlas_origin.y,
point_size: options.point_size,
pad: 0.0,
};
self.image_metadata[image_index] = ImageMetadata {
glyph_index: glyph_index as u32,
glyph_id: glyph_id,
horizontal_offset: options.horizontal_offset,
start_index: outlines.descriptor(glyph_index).unwrap().start_index(),
end_index: match outlines.descriptor(glyph_index + 1) {
Some(descriptor) => descriptor.start_index() as u32,
None => outlines.indices_count() as u32,
},
};
Ok(atlas_origin)
}
/// Uploads this batch data to the GPU.
fn create_batch(self) -> Result<Batch, GlError> {
let (mut current_range, mut counts, mut start_indices) = (None, vec![], vec![]);
for image_metadata in &self.image_metadata {
let (start_index, end_index) = (image_metadata.start_index, image_metadata.end_index);
if start_index == 0 {
continue
}
match current_range {
Some((current_first, current_last)) if start_index == current_last => {
current_range = Some((current_first, end_index))
}
Some((current_first, current_last)) => {
counts.push((current_last - current_first) as GLsizei);
start_indices.push((current_first as usize) * mem::size_of::<u32>());
current_range = Some((start_index, end_index))
}
None => current_range = Some((start_index, end_index)),
}
}
if let Some((current_first, current_last)) = current_range {
counts.push((current_last - current_first) as GLsizei);
start_indices.push((current_first as usize) * mem::size_of::<u32>());
}
// TODO(pcwalton): Try using `glMapBuffer` here.
unsafe {
let mut images = 0;
gl::GenBuffers(1, &mut images);
let length = self.image_descriptors.len() * mem::size_of::<ImageDescriptor>();
let ptr = self.image_descriptors.as_ptr() as *const ImageDescriptor as *const c_void;
gl::BindBuffer(gl::UNIFORM_BUFFER, images);
gl::BufferData(gl::UNIFORM_BUFFER, length as GLsizeiptr, ptr, gl::DYNAMIC_DRAW);
Ok(Batch {
images_buffer: images,
start_indices: start_indices,
counts: counts,
})
}
}
}
/// An atlas holding rendered glyphs on the GPU.
pub struct Atlas {
batches: Vec<Batch>,
shelf_height: u32,
shelf_columns: u32,
options: AtlasOptions,
}
impl Atlas {
#[doc(hidden)]
pub unsafe fn draw(&self, primitive: GLenum) {
for batch in &self.batches {
batch.draw(primitive)
}
}
/// Returns the width of this atlas in pixels.
#[inline]
pub fn width(&self) -> u32 {
self.options.available_width
}
/// Returns the height of each shelf.
#[inline]
pub fn shelf_height(&self) -> u32 {
self.shelf_height
}
#[doc(hidden)]
#[inline]
pub fn shelf_columns(&self) -> u32 {
self.shelf_columns
}
#[doc(hidden)]
#[inline]
pub fn uses_subpixel_antialiasing(&self) -> bool {
self.options.subpixel_antialiasing
}
/// Returns true if this atlas has no glyphs of nonzero size in it.
#[inline]
pub fn is_empty(&self) -> bool {
self.shelf_columns == 0
}
}
struct Batch {
images_buffer: GLuint,
start_indices: Vec<usize>,
counts: Vec<GLsizei>,
}
impl Drop for Batch {
fn drop(&mut self) {
unsafe {
gl::DeleteBuffers(1, &mut self.images_buffer);
}
}
}
impl Batch {
unsafe fn draw(&self, primitive: GLenum) {
debug_assert!(self.counts.len() == self.start_indices.len());
// The image descriptors are bound to binding point 2. See `draw.vs.glsl`.
gl::BindBufferBase(gl::UNIFORM_BUFFER, 2, self.images_buffer);
gl::MultiDrawElements(primitive,
self.counts.as_ptr(),
gl::UNSIGNED_INT,
self.start_indices.as_ptr() as *const *const GLvoid,
self.counts.len() as GLsizei);
}
}
/// Customizable attributes of the atlas.
#[derive(Clone, Copy, Debug, Default)]
pub struct AtlasOptions {
/// The width of the atlas.
///
/// This width can be any value at least as large as all glyphs in the font. It is recommended
/// to keep it fairly large in order to make efficient use of the space: 1024 or 2048 is a good
/// choice on modern GPUs.
pub available_width: u32,
/// The height of each shelf in the atlas.
///
/// The shelf height should be the maximum of all minimum shelf heights for all fonts you wish
/// to render into the atlas. You can retrive the minimum shelf height for a font with the
/// `Font::shelf_height()` method.
pub shelf_height: u32,
/// Whether subpixel antialiasing should be used.
pub subpixel_antialiasing: bool,
}
/// Options that control how a glyph is rasterized.
#[derive(Clone, Copy, Debug, Default)]
pub struct GlyphRasterizationOptions {
/// The point size.
pub point_size: f32,
/// How much space we should leave on the left side of the glyph, in pixels.
///
/// This is useful for rendering at a subpixel offset.
pub horizontal_offset: f32,
}
// Information about each image that we send to the GPU.
#[repr(C)]
#[doc(hidden)]
#[derive(Clone, Copy, Default, Debug)]
pub struct ImageDescriptor {
atlas_x: f32,
atlas_y: f32,
point_size: f32,
pad: f32,
}
// Information about each image that we keep around ourselves.
#[doc(hidden)]
#[derive(Clone, Copy, Default, Debug)]
pub struct ImageMetadata {
glyph_index: u32,
start_index: u32,
end_index: u32,
horizontal_offset: f32,
glyph_id: u16,
}

View File

@ -1,265 +0,0 @@
// Copyright 2017 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! A font's mapping from Unicode codepoints (characters) to glyphs.
//!
//! Consulting this table is typically the first step when rendering some text.
/// A consecutive series of Unicode codepoints.
#[derive(Clone, Copy, Debug)]
pub struct CodepointRange {
/// The starting code point, inclusive.
pub start: u32,
/// The ending code point, *inclusive*.
pub end: u32,
}
/// A collection of Unicode codepoints, organized into consecutive series.
#[derive(Clone, Debug)]
pub struct CodepointRanges {
/// Consecutive series of codepoints.
pub ranges: Vec<CodepointRange>,
}
impl CodepointRange {
/// Creates a new codepoint range from the given start and end codepoints, *inclusive*.
#[inline]
pub fn new(start: u32, end: u32) -> CodepointRange {
CodepointRange {
start: start,
end: end,
}
}
/// Returns an iterator that iterates over all codepoints in this range.
#[inline]
pub fn iter(&self) -> CodepointRangeIter {
CodepointRangeIter {
start: self.start,
end: self.end,
}
}
}
impl CodepointRanges {
/// Creates an empty set of codepoint ranges.
#[inline]
pub fn empty() -> CodepointRanges {
CodepointRanges {
ranges: vec![],
}
}
/// Creates codepoint ranges from a sorted array of characters, collapsing duplicates.
///
/// This is useful when creating an atlas from a string. The array can be readily produced with
/// an expression like `"Hello world".chars().collect()`.
pub fn from_sorted_chars(chars: &[char]) -> CodepointRanges {
let mut ranges: Vec<CodepointRange> = vec![];
for &ch in chars {
match ranges.last_mut() {
Some(ref mut range) if range.end == ch as u32 => continue,
Some(ref mut range) if range.end == ch as u32 + 1 => {
range.end += 1;
continue
}
_ => {}
}
ranges.push(CodepointRange::new(ch as u32, ch as u32))
}
CodepointRanges {
ranges: ranges,
}
}
}
/// An iterator over all codepoints in a range.
pub struct CodepointRangeIter {
start: u32,
end: u32,
}
impl Iterator for CodepointRangeIter {
type Item = u32;
#[inline]
fn next(&mut self) -> Option<u32> {
if self.start > self.end {
None
} else {
let item = self.start;
self.start += 1;
Some(item)
}
}
}
#[doc(hidden)]
#[derive(Clone, Copy, Debug)]
pub struct GlyphRange {
/// The starting glyph ID in the range, inclusive.
pub start: u16,
/// The ending glyph ID in the range, *inclusive*.
pub end: u16,
}
#[doc(hidden)]
#[derive(Clone, Copy, Debug)]
pub struct MappedGlyphRange {
pub codepoint_start: u32,
pub glyphs: GlyphRange,
}
/// A map from Unicode codepoints to glyph IDs.
#[derive(Clone, Debug)]
pub struct GlyphMapping {
ranges: Vec<MappedGlyphRange>,
}
impl GlyphMapping {
#[doc(hidden)]
#[inline]
pub fn new() -> GlyphMapping {
GlyphMapping {
ranges: vec![],
}
}
#[doc(hidden)]
#[inline]
pub fn push(&mut self, range: MappedGlyphRange) {
self.ranges.push(range)
}
#[inline]
pub fn iter(&self) -> GlyphMappingIter {
if self.ranges.is_empty() {
return GlyphMappingIter {
start: GlyphRangesIndex {
range_index: 0,
glyph_index: 0,
},
end: GlyphRangesIndex {
range_index: -1,
glyph_index: 0,
},
codepoint: 0,
ranges: &self.ranges,
}
}
GlyphMappingIter {
start: GlyphRangesIndex {
range_index: 0,
glyph_index: self.ranges[0].glyphs.start as i32,
},
end: GlyphRangesIndex {
range_index: self.ranges.len() as i32 - 1,
glyph_index: self.ranges.last().unwrap().glyphs.end as i32,
},
codepoint: self.ranges[0].codepoint_start,
ranges: &self.ranges,
}
}
pub fn glyph_for(&self, codepoint: u32) -> Option<u16> {
let (mut lo, mut hi) = (0, self.ranges.len());
while lo < hi {
let mid = (lo + hi) / 2;
if codepoint < self.ranges[mid].codepoint_start {
hi = mid
} else if codepoint > self.ranges[mid].codepoint_end() {
lo = mid + 1
} else {
return Some((codepoint - self.ranges[mid].codepoint_start) as u16 +
self.ranges[mid].glyphs.start)
}
}
None
}
}
#[derive(Clone)]
struct GlyphRangeIter {
start: u16,
end: u16,
}
impl Iterator for GlyphRangeIter {
type Item = u16;
#[inline]
fn next(&mut self) -> Option<u16> {
if self.start > self.end {
None
} else {
let item = self.start;
self.start += 1;
Some(item)
}
}
}
/// An iterator over the codepoint-to-glyph mapping.
///
/// Every call to `next()` returns a tuple consisting of the codepoint and glyph ID, in that order.
#[derive(Clone)]
pub struct GlyphMappingIter<'a> {
start: GlyphRangesIndex,
end: GlyphRangesIndex,
codepoint: u32,
ranges: &'a [MappedGlyphRange],
}
impl<'a> Iterator for GlyphMappingIter<'a> {
type Item = (u32, u16);
#[inline]
fn next(&mut self) -> Option<(u32, u16)> {
if self.start.range_index > self.end.range_index {
return None
}
let item = (self.codepoint, self.start.glyph_index as u16);
self.codepoint += 1;
self.start.glyph_index += 1;
while self.start.glyph_index > self.ranges[self.start.range_index as usize].glyphs.end as
i32 {
self.start.range_index += 1;
if self.start.range_index > self.end.range_index {
break
}
self.start.glyph_index = self.ranges[self.start.range_index as usize].glyphs.start as
i32;
self.codepoint = self.ranges[self.start.range_index as usize].codepoint_start;
}
Some(item)
}
}
#[derive(Clone, Copy, Debug)]
struct GlyphRangesIndex {
range_index: i32,
glyph_index: i32,
}
impl MappedGlyphRange {
/// Inclusive.
#[inline]
pub fn codepoint_end(&self) -> u32 {
self.codepoint_start + self.glyphs.end as u32 - self.glyphs.start as u32
}
}

View File

@ -1,100 +0,0 @@
// Copyright 2017 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! Apple Font Suitcase (`.dfont`) files.
//!
//! See: https://github.com/kreativekorp/ksfl/wiki/Macintosh-Resource-File-Format
use byteorder::{BigEndian, ReadBytesExt};
use error::FontError;
use font::Font;
use std::mem;
use util::Jump;
pub const MAGIC_NUMBER: u32 = 0x0100;
const SFNT: u32 = ((b's' as u32) << 24) |
((b'f' as u32) << 16) |
((b'n' as u32) << 8) |
(b't' as u32);
impl<'a> Font<'a> {
/// https://github.com/kreativekorp/ksfl/wiki/Macintosh-Resource-File-Format
pub fn from_dfont_index<'b>(bytes: &'b [u8], index: u32) -> Result<Font<'b>, FontError> {
let mut reader = bytes;
// Read the Mac resource file header.
let resource_data_offset = try!(reader.read_u32::<BigEndian>().map_err(FontError::eof));
let resource_map_offset = try!(reader.read_u32::<BigEndian>().map_err(FontError::eof));
let _resource_data_size = try!(reader.read_u32::<BigEndian>().map_err(FontError::eof));
let _resource_map_size = try!(reader.read_u32::<BigEndian>().map_err(FontError::eof));
// Move to the fields we care about in the resource map.
reader = bytes;
try!(reader.jump(resource_map_offset as usize + mem::size_of::<u32>() * 5 +
mem::size_of::<u16>() * 2).map_err(FontError::eof));
// Read the type list and name list offsets.
let type_list_offset = try!(reader.read_u16::<BigEndian>().map_err(FontError::eof));
let _name_list_offset = try!(reader.read_u16::<BigEndian>().map_err(FontError::eof));
// Move to the type list.
reader = bytes;
try!(reader.jump(resource_map_offset as usize + type_list_offset as usize)
.map_err(FontError::eof));
// Find the 'sfnt' type.
let type_count = (try!(reader.read_i16::<BigEndian>().map_err(FontError::eof)) + 1) as usize;
let mut resource_count_and_list_offset = None;
for _ in 0..type_count {
let type_id = try!(reader.read_u32::<BigEndian>().map_err(FontError::eof));
let resource_count = try!(reader.read_u16::<BigEndian>().map_err(FontError::eof));
let resource_list_offset = try!(reader.read_u16::<BigEndian>().map_err(FontError::eof));
if type_id == SFNT {
resource_count_and_list_offset = Some((resource_count, resource_list_offset));
break
}
}
// Unpack the resource count and list offset.
let resource_count;
match resource_count_and_list_offset {
None => return Err(FontError::Failed),
Some((count, resource_list_offset)) => {
resource_count = count;
reader = bytes;
try!(reader.jump(resource_map_offset as usize + type_list_offset as usize +
resource_list_offset as usize).map_err(FontError::eof));
}
}
// Check whether the index is in bounds.
if index >= resource_count as u32 + 1 {
return Err(FontError::FontIndexOutOfBounds)
}
// Find the font we're interested in.
try!(reader.jump(index as usize * (mem::size_of::<u16>() * 2 + mem::size_of::<u32>() * 2))
.map_err(FontError::eof));
let _sfnt_id = try!(reader.read_u16::<BigEndian>().map_err(FontError::eof));
let _sfnt_name_offset = try!(reader.read_u16::<BigEndian>().map_err(FontError::eof));
let sfnt_data_offset = try!(reader.read_u32::<BigEndian>().map_err(FontError::eof)) &
0x00ffffff;
let _sfnt_ptr = try!(reader.read_u32::<BigEndian>().map_err(FontError::eof));
// Load the resource.
reader = bytes;
try!(reader.jump(resource_data_offset as usize + sfnt_data_offset as usize)
.map_err(FontError::eof));
let sfnt_size = try!(reader.read_u32::<BigEndian>().map_err(FontError::eof));
Font::from_otf(&reader[0..sfnt_size as usize], 0)
}
}

View File

@ -1,17 +0,0 @@
// Copyright 2017 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! Various kinds of files that can contain OpenType fonts.
pub mod dfont;
pub mod otf;
pub mod ttc;
pub mod woff;

View File

@ -1,173 +0,0 @@
// Copyright 2017 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! OpenType `.otf` files.
//!
//! See Microsoft's spec: https://www.microsoft.com/typography/otspec/otff.htm
use byteorder::{BigEndian, ReadBytesExt};
use error::FontError;
use font::{Font, FontTable};
use std::mem;
use tables::cff::{self, CffTable};
use tables::cvt;
use tables::cmap::{self, CmapTable};
use tables::fpgm;
use tables::glyf::{self, GlyfTable};
use tables::head::{self, HeadTable};
use tables::hhea::{self, HheaTable};
use tables::hmtx::{self, HmtxTable};
use tables::kern::{self, KernTable};
use tables::loca::{self, LocaTable};
use tables::os_2::{self, Os2Table};
use tables::prep;
use util::Jump;
const OTTO: u32 = ((b'O' as u32) << 24) |
((b'T' as u32) << 16) |
((b'T' as u32) << 8) |
(b'O' as u32);
pub const KNOWN_TABLE_COUNT: usize = 12;
pub static KNOWN_TABLES: [u32; KNOWN_TABLE_COUNT] = [
cff::TAG,
os_2::TAG,
cmap::TAG,
cvt::TAG,
fpgm::TAG,
glyf::TAG,
head::TAG,
hhea::TAG,
hmtx::TAG,
kern::TAG,
loca::TAG,
prep::TAG,
];
// This must agree with the above.
const TABLE_INDEX_CFF: usize = 0;
const TABLE_INDEX_OS_2: usize = 1;
const TABLE_INDEX_CMAP: usize = 2;
const TABLE_INDEX_CVT: usize = 3;
const TABLE_INDEX_FPGM: usize = 4;
const TABLE_INDEX_GLYF: usize = 5;
const TABLE_INDEX_HEAD: usize = 6;
const TABLE_INDEX_HHEA: usize = 7;
const TABLE_INDEX_HMTX: usize = 8;
const TABLE_INDEX_KERN: usize = 9;
const TABLE_INDEX_LOCA: usize = 10;
const TABLE_INDEX_PREP: usize = 11;
pub static SFNT_VERSIONS: [u32; 3] = [
0x10000,
((b't' as u32) << 24) | ((b'r' as u32) << 16) | ((b'u' as u32) << 8) | (b'e' as u32),
OTTO,
];
#[doc(hidden)]
pub struct FontTables<'a> {
// Required tables.
pub cmap: CmapTable<'a>,
pub head: HeadTable,
pub hhea: HheaTable,
pub hmtx: HmtxTable<'a>,
pub os_2: Os2Table,
// Optional tables.
pub cff: Option<CffTable<'a>>,
pub glyf: Option<GlyfTable<'a>>,
pub loca: Option<LocaTable<'a>>,
pub kern: Option<KernTable<'a>>,
// Optional tables that need no parsing.
pub cvt: Option<FontTable<'a>>,
pub fpgm: Option<FontTable<'a>>,
pub prep: Option<FontTable<'a>>,
}
impl<'a> Font<'a> {
pub fn from_otf<'b>(bytes: &'b [u8], offset: u32) -> Result<Font<'b>, FontError> {
let mut reader = bytes;
try!(reader.jump(offset as usize).map_err(FontError::eof));
// Check the magic number.
if !SFNT_VERSIONS.contains(&try!(reader.read_u32::<BigEndian>().map_err(FontError::eof))) {
return Err(FontError::UnknownFormat)
}
let num_tables = try!(reader.read_u16::<BigEndian>().map_err(FontError::eof));
try!(reader.jump(mem::size_of::<u16>() * 3).map_err(FontError::eof));
let mut tables = [None; KNOWN_TABLE_COUNT];
for _ in 0..num_tables {
let table_id = try!(reader.read_u32::<BigEndian>().map_err(FontError::eof));
let _checksum = try!(reader.read_u32::<BigEndian>().map_err(FontError::eof));
let offset = try!(reader.read_u32::<BigEndian>().map_err(FontError::eof)) as usize;
let length = try!(reader.read_u32::<BigEndian>().map_err(FontError::eof)) as usize;
// Find the table ID in our list of known IDs, which must be sorted.
debug_assert!(KNOWN_TABLES.windows(2).all(|w| w[0] < w[1]));
let slot = match KNOWN_TABLES.binary_search(&table_id) {
Err(_) => continue,
Ok(table_index) => &mut tables[table_index],
};
// Make sure there isn't more than one copy of the table.
if slot.is_some() {
return Err(FontError::Failed)
}
*slot = Some(FontTable {
bytes: &bytes[offset..offset + length],
})
}
Font::from_table_list(bytes, &tables)
}
#[doc(hidden)]
pub fn from_table_list<'b>(bytes: &'b [u8],
tables: &[Option<FontTable<'b>>; KNOWN_TABLE_COUNT])
-> Result<Font<'b>, FontError> {
let cff_table = match tables[TABLE_INDEX_CFF] {
None => None,
Some(cff_table) => Some(try!(CffTable::new(cff_table))),
};
let loca_table = match tables[TABLE_INDEX_LOCA] {
None => None,
Some(loca_table) => Some(try!(LocaTable::new(loca_table))),
};
// For brevity below…
let missing = FontError::RequiredTableMissing;
let tables = FontTables {
cmap: CmapTable::new(try!(tables[TABLE_INDEX_CMAP].ok_or(missing))),
head: try!(HeadTable::new(try!(tables[TABLE_INDEX_HEAD].ok_or(missing)))),
hhea: try!(HheaTable::new(try!(tables[TABLE_INDEX_HHEA].ok_or(missing)))),
hmtx: HmtxTable::new(try!(tables[TABLE_INDEX_HMTX].ok_or(missing))),
os_2: try!(Os2Table::new(try!(tables[TABLE_INDEX_OS_2].ok_or(missing)))),
cff: cff_table,
glyf: tables[TABLE_INDEX_GLYF].map(GlyfTable::new),
loca: loca_table,
kern: tables[TABLE_INDEX_KERN].and_then(|table| KernTable::new(table).ok()),
cvt: tables[TABLE_INDEX_CVT],
fpgm: tables[TABLE_INDEX_FPGM],
prep: tables[TABLE_INDEX_PREP],
};
Ok(Font::from_tables(bytes, tables))
}
}

View File

@ -1,52 +0,0 @@
// Copyright 2017 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! TrueType Collection (`.ttc`) files.
//!
//! See Microsoft's spec: https://www.microsoft.com/typography/otspec/otff.htm
use byteorder::{BigEndian, ReadBytesExt};
use error::FontError;
use font::Font;
use std::mem;
use util::Jump;
pub const MAGIC_NUMBER: u32 = ((b't' as u32) << 24) |
((b't' as u32) << 16) |
((b'c' as u32) << 8) |
(b'f' as u32);
impl<'a> Font<'a> {
/// Creates a new font from a single font within a byte buffer containing the contents of a
/// font collection.
pub fn from_ttc_index<'b>(bytes: &'b [u8], index: u32) -> Result<Font<'b>, FontError> {
let mut reader = bytes;
let magic_number = try!(reader.read_u32::<BigEndian>().map_err(FontError::eof));
if magic_number != MAGIC_NUMBER {
return Err(FontError::UnknownFormat)
}
let major_version = try!(reader.read_u16::<BigEndian>().map_err(FontError::eof));
let minor_version = try!(reader.read_u16::<BigEndian>().map_err(FontError::eof));
if (major_version != 1 && major_version != 2) || minor_version != 0 {
return Err(FontError::UnsupportedVersion)
}
let num_fonts = try!(reader.read_u32::<BigEndian>().map_err(FontError::eof));
if index >= num_fonts {
return Err(FontError::FontIndexOutOfBounds)
}
try!(reader.jump(index as usize * mem::size_of::<u32>()).map_err(FontError::eof));
let table_offset = try!(reader.read_u32::<BigEndian>().map_err(FontError::eof));
Font::from_otf(&bytes, table_offset)
}
}

View File

@ -1,114 +0,0 @@
// Copyright 2017 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! Web Open Font Format 1.0 (`.woff`) files.
//!
//! See the specification: https://www.w3.org/TR/WOFF/
//!
//! TODO(pcwalton): WOFF 2.0.
use byteorder::{BigEndian, ReadBytesExt};
use containers::otf::{KNOWN_TABLES, KNOWN_TABLE_COUNT, SFNT_VERSIONS};
use error::FontError;
use flate2::FlateReadExt;
use font::{Font, FontTable};
use std::io::Read;
use std::iter;
use std::mem;
use util::Jump;
pub const MAGIC_NUMBER: u32 = ((b'w' as u32) << 24) |
((b'O' as u32) << 16) |
((b'F' as u32) << 8) |
(b'F' as u32);
impl<'a> Font<'a> {
/// Creates a new font from a buffer containing data in the WOFF format.
///
/// The given buffer will be used to decompress data.
///
/// Decompresses eagerly.
pub fn from_woff<'b>(bytes: &'b [u8], buffer: &'b mut Vec<u8>) -> Result<Font<'b>, FontError> {
let mut reader = bytes;
// Check magic number.
let magic_number = try!(reader.read_u32::<BigEndian>().map_err(FontError::eof));
if magic_number != MAGIC_NUMBER {
return Err(FontError::UnknownFormat)
}
// Check the flavor.
let flavor = try!(reader.read_u32::<BigEndian>().map_err(FontError::eof));
if !SFNT_VERSIONS.contains(&flavor) {
return Err(FontError::UnknownFormat)
}
// Get the number of tables.
let _length = try!(reader.read_u32::<BigEndian>().map_err(FontError::eof));
let num_tables = try!(reader.read_u16::<BigEndian>().map_err(FontError::eof));
let _reserved = try!(reader.read_u16::<BigEndian>().map_err(FontError::eof));
// Allocate size for uncompressed tables.
let total_sfnt_size = try!(reader.read_u32::<BigEndian>().map_err(FontError::eof));
try!(reader.jump(mem::size_of::<u32>() * 6).map_err(FontError::eof));
let buffer_start = buffer.len();
buffer.extend(iter::repeat(0).take(total_sfnt_size as usize));
let mut buffer = &mut buffer[buffer_start..];
// Decompress and load tables as necessary.
let mut tables = [None; KNOWN_TABLE_COUNT];
for _ in 0..num_tables {
let tag = try!(reader.read_u32::<BigEndian>().map_err(FontError::eof));
let offset = try!(reader.read_u32::<BigEndian>().map_err(FontError::eof));
let comp_length = try!(reader.read_u32::<BigEndian>().map_err(FontError::eof));
let orig_length = try!(reader.read_u32::<BigEndian>().map_err(FontError::eof));
let _orig_checksum = try!(reader.read_u32::<BigEndian>().map_err(FontError::eof));
// Find the table ID in our list of known IDs, which must be sorted.
debug_assert!(KNOWN_TABLES.windows(2).all(|w| w[0] < w[1]));
let slot = match KNOWN_TABLES.binary_search(&tag) {
Err(_) => continue,
Ok(table_index) => &mut tables[table_index],
};
// Make sure there isn't more than one copy of the table.
if slot.is_some() {
return Err(FontError::Failed)
}
// Allocate space in the buffer.
let comp_end = offset as usize + comp_length as usize;
let mut temp = buffer; // borrow check black magic
let (mut dest, mut rest) = temp.split_at_mut(orig_length as usize);
buffer = rest;
// Decompress or copy as applicable.
//
// FIXME(pcwalton): Errors here may be zlib errors, not EOFs.
if comp_length != orig_length {
let mut table_reader = bytes;
try!(table_reader.jump(offset as usize).map_err(FontError::eof));
let mut table_reader = table_reader.zlib_decode();
try!(table_reader.read_exact(dest).map_err(FontError::eof));
} else if comp_end <= bytes.len() {
dest.clone_from_slice(&bytes[offset as usize..comp_end])
} else {
return Err(FontError::UnexpectedEof)
}
*slot = Some(FontTable {
bytes: dest,
})
}
Font::from_table_list(bytes, &tables)
}
}

View File

@ -1,133 +0,0 @@
// Copyright 2017 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! An intermediate surface on the GPU used during the rasterization process.
use compute_shader::buffer::Protection;
use compute_shader::device::Device;
use compute_shader::image::{ExternalImage, Format, Image};
use error::InitError;
use euclid::size::Size2D;
use gl::types::{GLint, GLuint};
use gl;
const DEFAULT_COVERAGE_BUFFER_SIZE: u32 = 1024;
/// An intermediate surface on the GPU used during the rasterization process.
///
/// You can reuse this surface from draw operation to draw operation. It only needs to be at least
/// as large as every atlas you will draw into it.
///
/// The GPU memory usage of this buffer is `4 * width * height` bytes.
pub struct CoverageBuffer {
image: Image,
framebuffer: GLuint,
}
impl CoverageBuffer {
/// Creates a new coverage buffer with the given options.
pub fn new(device: &Device, options: &CoverageBufferOptions)
-> Result<CoverageBuffer, InitError> {
let mut size = options.size;
if options.subpixel_antialiasing {
size.width *= 3
}
let image = try!(device.create_image(Format::R32F, Protection::ReadWrite, &size)
.map_err(InitError::ComputeError));
let mut framebuffer = 0;
unsafe {
let mut gl_texture = 0;
gl::GenTextures(1, &mut gl_texture);
try!(image.bind_to(&ExternalImage::GlTexture(gl_texture))
.map_err(InitError::ComputeError));
gl::BindTexture(gl::TEXTURE_RECTANGLE, gl_texture);
gl::TexParameteri(gl::TEXTURE_RECTANGLE, gl::TEXTURE_MIN_FILTER, gl::LINEAR as GLint);
gl::TexParameteri(gl::TEXTURE_RECTANGLE, gl::TEXTURE_MAG_FILTER, gl::LINEAR as GLint);
gl::TexParameteri(gl::TEXTURE_RECTANGLE,
gl::TEXTURE_WRAP_S,
gl::CLAMP_TO_EDGE as GLint);
gl::TexParameteri(gl::TEXTURE_RECTANGLE,
gl::TEXTURE_WRAP_T,
gl::CLAMP_TO_EDGE as GLint);
gl::GenFramebuffers(1, &mut framebuffer);
gl::BindFramebuffer(gl::FRAMEBUFFER, framebuffer);
gl::FramebufferTexture2D(gl::FRAMEBUFFER,
gl::COLOR_ATTACHMENT0,
gl::TEXTURE_RECTANGLE,
gl_texture,
0);
gl::BindFramebuffer(gl::FRAMEBUFFER, 0);
}
Ok(CoverageBuffer {
image: image,
framebuffer: framebuffer,
})
}
#[doc(hidden)]
#[inline]
pub fn image(&self) -> &Image {
&self.image
}
#[doc(hidden)]
#[inline]
pub fn framebuffer(&self) -> GLuint {
self.framebuffer
}
}
impl Drop for CoverageBuffer {
fn drop(&mut self) {
unsafe {
let mut gl_texture = 0;
gl::BindFramebuffer(gl::FRAMEBUFFER, self.framebuffer);
gl::GetFramebufferAttachmentParameteriv(gl::FRAMEBUFFER,
gl::COLOR_ATTACHMENT0,
gl::FRAMEBUFFER_ATTACHMENT_OBJECT_NAME,
&mut gl_texture as *mut GLuint as *mut GLint);
gl::DeleteTextures(1, &mut gl_texture);
gl::BindFramebuffer(gl::FRAMEBUFFER, 0);
gl::DeleteFramebuffers(1, &mut self.framebuffer);
}
}
}
/// Options that control the format of the coverage buffer.
#[derive(Clone, Copy, Debug)]
pub struct CoverageBufferOptions {
/// The size of the coverage buffer.
///
/// The size must be at least as large as every atlas you will render with the buffer.
pub size: Size2D<u32>,
/// Whether this coverage buffer is intended for subpixel antialiasing.
///
/// If this buffer is intended for subpixel AA, all atlas rendered with it must use subpixel
/// AA, and vice versa.
pub subpixel_antialiasing: bool,
}
impl Default for CoverageBufferOptions {
#[inline]
fn default() -> CoverageBufferOptions {
CoverageBufferOptions {
size: Size2D::new(DEFAULT_COVERAGE_BUFFER_SIZE, DEFAULT_COVERAGE_BUFFER_SIZE),
subpixel_antialiasing: false,
}
}
}

View File

@ -1,179 +0,0 @@
// Copyright 2017 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! Errors.
use compute_shader;
use gl::types::GLenum;
use std::io;
/// Errors that can occur when parsing OpenType fonts.
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum FontError {
/// A miscellaneous error occurred.
Failed,
/// The file ended unexpectedly.
UnexpectedEof,
/// There is no font with this index in this font collection.
FontIndexOutOfBounds,
/// The file declared that it was in a version of the format we don't support.
UnsupportedVersion,
/// The file was of a format we don't support.
UnknownFormat,
/// The font had a glyph format we don't support.
UnsupportedGlyphFormat,
/// We don't support the declared version of the font's CFF outlines.
UnsupportedCffVersion,
/// We don't support the declared version of the font's character map.
UnsupportedCmapVersion,
/// The font character map has an unsupported platform/encoding ID.
UnsupportedCmapEncoding,
/// The font character map has an unsupported format.
UnsupportedCmapFormat,
/// We don't support the declared version of the font header.
UnsupportedHeadVersion,
/// We don't support the declared version of the font's horizontal metrics.
UnsupportedHheaVersion,
/// We don't support the declared version of the font's OS/2 and Windows table.
UnsupportedOs2Version,
/// A required table is missing.
RequiredTableMissing,
/// An integer in a CFF DICT was not found.
CffIntegerNotFound,
/// The CFF Top DICT was not found.
CffTopDictNotFound,
/// A CFF `Offset` value was formatted incorrectly.
CffBadOffset,
/// The CFF evaluation stack overflowed.
CffStackOverflow,
/// An unimplemented CFF CharString operator was encountered.
CffUnimplementedOperator,
}
impl FontError {
#[doc(hidden)]
#[inline]
pub fn eof<T>(_: T) -> FontError {
FontError::UnexpectedEof
}
}
/// An OpenGL error with the given code.
///
/// You cannot depend on these being reliably returned. Pathfinder does not call `glGetError()`
/// unless necessary, to avoid driver stalls.
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct GlError(pub GLenum);
/// An initialization error. This could be an OpenGL error or a shader compilation/link error.
#[derive(Debug)]
pub enum InitError {
/// An OpenGL error occurred.
GlError(GlError),
/// A shader could not be loaded.
ShaderUnreadable(io::Error),
/// Shader compilation failed.
///
/// The first string specifies the type of shader (vertex, fragment, etc.); the second holds
/// the error message that the driver returned.
CompileFailed(&'static str, String),
/// Shader linking failed.
///
/// The string holds the error message that the driver returned.
LinkFailed(String),
/// An error occurred setting up GPU compute.
ComputeError(compute_shader::error::Error),
/// One of the rasterization options had an invalid syntax.
InvalidSetting,
}
/// A rasterization error. This could be an OpenGL error or a compute error.
#[derive(Debug)]
pub enum RasterError {
/// No glyphs were supplied.
NoGlyphsToDraw,
/// An OpenGL error occurred.
GlError(GlError),
/// An error occurred during GPU compute.
ComputeError(compute_shader::error::Error),
/// An destination image with an unsupported format was supplied.
///
/// Currently supported formats are R8 and RGBA8.
UnsupportedImageFormat,
}
/// An error in glyph store creation. See `Typesetter::create_glyph_store()`.
#[derive(Debug)]
pub enum GlyphStoreCreationError {
/// An error occurred when looking up a glyph ID for a character in the font.
FontError(FontError),
/// An error occurred when uploading the outlines to the GPU.
GlError(GlError),
}
/// An error in construction of a hinter.
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum HinterCreationError {
/// A miscellaneous error occurred.
Failed,
/// An error was encountered while analyzing the font program.
FontProgramAnalysisError(HintingAnalysisError),
/// An error was encountered while analyzing the control value program.
ControlValueProgramAnalysisError(HintingAnalysisError),
/// An error was encountered during execution of the font program.
FontProgramExecutionError(HintingExecutionError),
}
/// An error encountered during parsing of the TrueType hinting bytecode.
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum HintingParseError {
/// The instruction stream terminated normally.
Eof,
/// The instruction stream terminated abnormally.
UnexpectedEof,
/// An unexpected opcode was encountered.
UnknownOpcode(u8),
/// An unexpected value was encountered for `DistanceType`.
InvalidDistanceType,
}
/// An error encountered during semantic analysis of the TrueType hinting bytecode.
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum HintingAnalysisError {
/// An error occurred while parsing the instruction stream.
ParseError(HintingParseError),
/// A branch target (e.g. `Eif`) was found without a corresponding branch instruction.
BranchTargetMissingBranch,
/// A branch target was (e.g. `If`) was found without a corresponding branch target (e.g.
/// `Eif`).
BranchMissingBranchTarget,
/// A branch target was mismatched with its branch instruction (`Eif` vs. `If`, etc.)
MismatchedBranchInstruction,
}
/// An error encountered during execution of the TrueType hinting bytecode.
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum HintingExecutionError {
/// An error occurred while parsing the instruction stream.
ParseError(HintingParseError),
/// An instruction expected more values than were on the stack.
StackUnderflow,
/// An operation tried to read out of bounds of the control value table.
CvtReadOutOfBounds,
/// An undefined function ID was called.
CallToUndefinedFunction,
}

View File

@ -1,259 +0,0 @@
// Copyright 2017 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! OpenType fonts.
use byteorder::{BigEndian, ReadBytesExt};
use charmap::{CodepointRange, GlyphMapping};
use containers::dfont;
use containers::otf::{FontTables, SFNT_VERSIONS};
use containers::ttc;
use containers::woff;
use error::FontError;
use euclid::Point2D;
use outline::GlyphBounds;
use tables::hmtx::HorizontalMetrics;
/// A handle to a font backed by a byte buffer containing the contents of the file (`.ttf`,
/// `.otf`), etc.
///
/// For optimum performance, consider using the `memmap` crate to provide the byte buffer.
pub struct Font<'a> {
pub bytes: &'a [u8],
tables: FontTables<'a>,
}
#[doc(hidden)]
#[derive(Clone, Copy, Debug)]
pub struct FontTable<'a> {
pub bytes: &'a [u8],
}
impl<'a> Font<'a> {
#[doc(hidden)]
pub fn from_tables<'b>(bytes: &'b [u8], tables: FontTables<'b>) -> Font<'b> {
Font {
bytes: bytes,
tables: tables,
}
}
/// Creates a new font from a byte buffer containing the contents of a file or font collection
/// (`.ttf`, `.ttc`, `.otf`, etc.)
///
/// If this is a `.ttc` or `.dfont` collection, this returns the first font within it. If you
/// want to read another one, use the `Font::from_collection_index` API.
///
/// The supplied `buffer` is an arbitrary vector that may or may not be used as a temporary
/// storage space. Typically you will want to just pass an empty vector here.
///
/// Returns the font on success or an error on failure.
pub fn new<'b>(bytes: &'b [u8], buffer: &'b mut Vec<u8>) -> Result<Font<'b>, FontError> {
Font::from_collection_index(bytes, 0, buffer)
}
/// Creates a new font from a single font within a byte buffer containing the contents of a
/// file or a font collection (`.ttf`, `.ttc`, `.otf`, etc.)
///
/// If this is a `.ttc` or `.dfont` collection, this returns the appropriate font within it.
///
/// The supplied `buffer` is an arbitrary vector that may or may not be used as a temporary
/// storage space. Typically you will want to just pass an empty vector here.
///
/// Returns the font on success or an error on failure.
pub fn from_collection_index<'b>(bytes: &'b [u8], index: u32, buffer: &'b mut Vec<u8>)
-> Result<Font<'b>, FontError> {
// Check the magic number.
let mut reader = bytes;
let magic_number = try!(reader.read_u32::<BigEndian>().map_err(FontError::eof));
match magic_number {
ttc::MAGIC_NUMBER => Font::from_ttc_index(bytes, index),
woff::MAGIC_NUMBER => Font::from_woff(bytes, buffer),
dfont::MAGIC_NUMBER => Font::from_dfont_index(bytes, index),
magic_number if SFNT_VERSIONS.contains(&magic_number) => Font::from_otf(bytes, 0),
_ => Err(FontError::UnknownFormat),
}
}
/// Returns the glyph IDs that map to the given ranges of Unicode codepoints.
///
/// The returned glyph ranges are in the same order as the codepoints.
#[inline]
pub fn glyph_mapping_for_codepoint_ranges(&self, codepoint_ranges: &[CodepointRange])
-> Result<GlyphMapping, FontError> {
self.tables.cmap.glyph_mapping_for_codepoint_ranges(codepoint_ranges)
}
/// Calls the given callback for each point in the supplied glyph's contour.
///
/// This function is the primary method for accessing a glyph's outline.
#[inline]
pub fn for_each_point<F>(&self, glyph_id: u16, callback: F) -> Result<(), FontError>
where F: FnMut(&Point) {
match (self.tables.glyf, self.tables.cff) {
(Some(glyf), None) => {
let loca = match self.tables.loca {
Some(ref loca) => loca,
None => return Err(FontError::RequiredTableMissing),
};
glyf.for_each_point(&self.tables.head, loca, glyph_id, callback)
}
(None, Some(cff)) => cff.for_each_point(glyph_id, callback),
(Some(_), Some(_)) => Err(FontError::Failed),
(None, None) => Ok(()),
}
}
/// Returns the boundaries of the given glyph in font units.
#[inline]
pub fn glyph_bounds(&self, glyph_id: u16) -> Result<GlyphBounds, FontError> {
match (self.tables.glyf, self.tables.cff) {
(Some(glyf), None) => {
let loca = match self.tables.loca {
Some(ref loca) => loca,
None => return Err(FontError::RequiredTableMissing),
};
glyf.glyph_bounds(&self.tables.head, loca, glyph_id)
}
(None, Some(cff)) => cff.glyph_bounds(glyph_id),
(Some(_), Some(_)) => Err(FontError::Failed),
(None, None) => Err(FontError::RequiredTableMissing),
}
}
/// Returns the minimum shelf height that an atlas containing glyphs from this font will need.
#[inline]
pub fn shelf_height(&self, point_size: f32) -> u32 {
// Add 2 to account for the border.
self.tables.head
.max_glyph_bounds
.subpixel_bounds(self.tables.head.units_per_em, point_size)
.round_out()
.size()
.height as u32 + 2
}
/// Returns the number of font units per em.
///
/// An em is traditionally the width of the lowercase letter "m". A typical point size of a
/// font is expressed in number of pixels per em. Thus, in order to convert font units to
/// pixels, you can use an expression like `units * font_size / font.units_per_em()`.
#[inline]
pub fn units_per_em(&self) -> u16 {
self.tables.head.units_per_em
}
/// Returns the horizontal metrics for the glyph with the given ID.
///
/// Horizontal metrics are important for text shaping, as they specify the number of units to
/// advance the pen after typesetting a glyph.
#[inline]
pub fn metrics_for_glyph(&self, glyph_id: u16) -> Result<HorizontalMetrics, FontError> {
self.tables.hmtx.metrics_for_glyph(&self.tables.hhea, glyph_id)
}
/// Returns the kerning between the given two glyph IDs in font units.
///
/// Positive values move glyphs farther apart; negative values move glyphs closer together.
///
/// Zero is returned if no kerning is available in the font.
#[inline]
pub fn kerning_for_glyph_pair(&self, left_glyph_id: u16, right_glyph_id: u16) -> i16 {
match self.tables.kern {
None => 0,
Some(kern) => kern.kerning_for_glyph_pair(left_glyph_id, right_glyph_id).unwrap_or(0),
}
}
/// Returns the distance from the baseline to the top of the text box in font units.
///
/// The following expression computes the baseline-to-baseline height:
/// `font.ascender() - font.descender() + font.line_gap()`.
#[inline]
pub fn ascender(&self) -> i16 {
self.tables.os_2.typo_ascender
}
/// Returns the distance from the baseline to the bottom of the text box in font units.
///
/// The following expression computes the baseline-to-baseline height:
/// `font.ascender() - font.descender() + font.line_gap()`.
#[inline]
pub fn descender(&self) -> i16 {
self.tables.os_2.typo_descender
}
/// Returns the recommended extra gap between lines in font units.
///
/// The following expression computes the baseline-to-baseline height:
/// `font.ascender() - font.descender() + font.line_gap()`.
#[inline]
pub fn line_gap(&self) -> i16 {
self.tables.os_2.typo_line_gap
}
/// Returns the Control Value Table of the font.
#[inline]
pub fn control_value_table(&self) -> &[u8] {
match self.tables.cvt {
None => &[],
Some(cvt) => cvt.bytes,
}
}
/// Returns the font program, which is run whenever the font is loaded.
#[inline]
pub fn font_program(&self) -> &[u8] {
match self.tables.fpgm {
None => &[],
Some(fpgm) => fpgm.bytes,
}
}
/// Returns the control value program, which is run whenever the point size changes.
#[inline]
pub fn control_value_program(&self) -> &[u8] {
match self.tables.prep {
None => &[],
Some(prep) => prep.bytes,
}
}
}
#[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,
/// 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,
}

View File

@ -1,521 +0,0 @@
// Copyright 2017 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! TrueType instructions.
use error::HintingParseError;
use euclid::Point2D;
use util::{F2DOT14_ONE, F2DOT14_ZERO, F2Dot14};
/// All TrueType instructions.
#[derive(Clone, Copy, Debug)]
pub enum Instruction<'a> {
/// Push N Bytes (0x40) (ttinst1.doc, p. 189)
/// Push Bytes (0xb0-0xb7) (ttinst1.doc, p. 191)
Pushb(&'a [u8]),
/// Push N Words (0x41) (ttinst1.doc, p. 190)
/// Push Words (0xb8-0xbf) (ttinst1.doc, p. 192)
Pushw(&'a [u8]),
/// Read Store (0x43) (ttinst1.doc, p. 194)
Rs,
/// Write Store (0x42) (ttinst1.doc, p. 195)
Ws,
/// Write Control Value Table in Pixel Units (0x44) (ttinst1.doc, 197)
Wcvtp,
/// Write Control Value Table in FUints (0x70) (ttinst1.doc, 198)
Wcvtf,
/// Read Control Value Table (0x45) (ttinst1.doc, 199)
Rcvt,
/// Set Freedom and Projection Vectors to Coordinate Axis (0x00-0x01) (ttinst1.doc, 202)
Svtca(Axis),
/// Set Projection Vector to Coordinate Axis (0x02-0x03) (ttinst1.doc, 203)
Spvtca(Axis),
/// Set Freedom Vector to Coordinate Axis (0x04-0x05) (ttinst1.doc, 204)
Sfvtca(Axis),
/// Set Projection Vector to Line (0x06-0x07) (ttinst1.doc, 205-207)
Spvtl(LineOrientation),
/// Set Freedom Vector to Line (0x08-0x09) (ttinst1.doc, 208-209)
Sfvtl(LineOrientation),
/// Set Freedom Vector to Projection Vector (0x0e) (ttinst1.doc, 210)
Sfvtpv,
/// Set Dual Projection Vector to Line (0x86-0x87) (ttinst1.doc, 211)
Sdpvtl(LineOrientation),
/// Set Projection Vector From Stack (0x0a) (ttinst1.doc, 212-213)
Spvfs,
/// Set Freedom Vector From Stack (0x0b) (ttinst1.doc, 214-215)
Sfvfs,
/// Get Projection Vector (0x0c) (ttinst1.doc, 216-217)
Gpv,
/// Get Freedom Vector (0x0d) (ttinst1.doc, 218-219)
Gfv,
/// Set Reference Point 0 (0x10) (ttinst1.doc, 220)
Srp0,
/// Set Reference Point 1 (0x11) (ttinst1.doc, 221)
Srp1,
/// Set Reference Point 2 (0x12) (ttinst1.doc, 222)
Srp2,
/// Set Zone Pointer 0 (0x13) (ttinst1.doc, 223)
Szp0,
/// Set Zone Pointer 1 (0x14) (ttinst1.doc, 224)
Szp1,
/// Set Zone Pointer 2 (0x15) (ttinst1.doc, 225)
Szp2,
/// Set Zone Pointers (0x16) (ttinst1.doc, 226)
Szps,
/// Round to Half Grid (0x19) (ttinst1.doc, 227)
Rthg,
/// Round to Grid (0x18) (ttinst1.doc, 228)
Rtg,
/// Round to Double Grid (0x3d) (ttinst1.doc, 229)
Rtdg,
/// Round Down to Grid (0x7d) (ttinst1.doc, 230)
Rdtg,
/// Round Up to Grid (0x7c) (ttinst1.doc, 231)
Rutg,
/// Round Off (0x7a) (ttinst1.doc, 232)
Roff,
/// Super Round (0x76) (ttinst1.doc, 233-238)
Sround,
/// Super Round 45 Degrees (0x77) (ttinst1.doc, 239)
S45round,
/// Set Loop Variable (0x17) (ttinst1.doc, 240)
Sloop,
/// Set Minimum Distance (0x1a) (ttinst1.doc, 241)
Smd,
/// Instruction Execution Control (0x8e) (ttinst1.doc, 242-243)
Instctrl,
/// Scan Conversion Control (0x85) (ttinst1.doc, 244-245)
Scanctrl,
/// ScanType (0x8d) (ttinst1.doc, 246)
Scantype,
/// Set Control Value Table Cut In (0x1d) (ttinst1.doc, 249)
Scvtci,
/// Set Single Width Cut In (0x1e) (ttinst1.doc, 250)
Sswci,
/// Set Single Width (0x1f) (ttinst1.doc, 251)
Ssw,
/// Set Auto Flip Boolean to On (0x4d) (ttinst1.doc, 252)
Flipon,
/// Set Auto Flip Boolean to Off (0x4e) (ttinst1.doc, 253)
Flipoff,
/// Set Angle Weight (0x7e) (ttinst1.doc, 254)
Sangw,
/// Set Delta Base (0x5e) (ttinst1.doc, 255)
Sdb,
/// Set Delta Shift (0x5f) (ttinst1.doc, 256)
Sds,
/// Get Coordinate (0x46-0x47) (ttinst1.doc, 258-259)
Gc(WhichPosition),
/// Set Coordinate From Stack (0x48) (ttinst1.doc, 260)
Scfs,
/// Measure Distance (0x49-0x4a) (ttinst1.doc, 261-262)
Md(WhichPosition),
/// Measure Pixels Per Em (0x4b) (ttinst1.doc, 263)
Mppem,
/// Measure Point Size (0x4c) (ttinst1.doc, 264)
Mps,
/// Flip Point (0x80) (ttinst2.doc, 263)
Flippt,
/// Flip Range On (0x81) (ttinst2.doc, 264)
Fliprgon,
/// Flip Range Off (0x82) (ttinst2.doc, 265)
Fliprgoff,
/// Shift Point by Last Point (0x32-0x33) (ttinst2.doc, 266)
Shp(ZonePoint),
/// Shift Contour by Last Point (0x34-0x35) (ttinst2.doc, 267)
Shc(ZonePoint),
/// Shift Zone by Last Point (0x36-0x37) (ttinst2.doc, 268)
Shz(ZonePoint),
/// Shift Point by Pixel Amount (0x38) (ttinst2.doc, 269)
Shpix,
/// Move Stack Indirect Relative Point (0x3a-0x3b) (ttinst2.doc, 270)
Msirp(SetRP0),
/// Move Direct Absolute Point (0x2e-0x2f) (ttinst2.doc, 271)
Mdap(ShouldRound),
/// Move Indirect Absolute Point (0x3e-0x3f) (ttinst2.doc, 272-275)
Miap(ShouldRound),
/// Move Direct Relative Point (0xc0-0xdf) (ttinst2.doc, 276-278)
Mdrp(SetRP0, ApplyMinimumDistance, ShouldRound, DistanceType),
/// Move Indirect Relative Point (0xe0-0xff) (ttinst2.doc, 279-283)
Mirp(SetRP0, ApplyMinimumDistance, ShouldRound, DistanceType),
/// Align Relative Point (0x3c) (ttinst2.doc, 284)
Alignrp,
/// Move Point to Intersection of Two Lines (0x0f) (ttinst2.doc, 286-288)
Isect,
/// Align Points (0x27) (ttinst2.doc, 289)
Alignpts,
/// Interpolate Point by Last Relative Stretch (0x39) (ttinst2.doc, 290)
Ip,
/// Untouch Point (0x29) (ttinst2.doc, 291)
Utp,
/// Interpolate Untouched Points Through Outline (0x30-0x31) (ttinst2.doc, 292)
Iup(Axis),
/// Delta Exception P1 (0x5d) (ttinst2.doc, 296)
Deltap1,
/// Delta Exception P2 (0x71) (ttinst2.doc, 297)
Deltap2,
/// Delta Exception P3 (0x72) (ttinst2.doc, 298)
Deltap3,
/// Delta Exception C1 (0x73) (ttinst2.doc, 299)
Deltac1,
/// Delta Exception C2 (0x74) (ttinst2.doc, 300)
Deltac2,
/// Delta Exception C3 (0x75) (ttinst2.doc, 301)
Deltac3,
/// Duplicate Top Stack Element (0x20) (ttinst2.doc, 304)
Dup,
/// Pop Top Stack Element (0x21) (ttinst2.doc, 305)
Pop,
/// Clear the Entire Stack (0x22) (ttinst2.doc, 306)
Clear,
/// Swap the Top Two Elements on the Stack (0x23) (ttinst2.doc, 307)
Swap,
/// Return the Depth of the Stack (0x24) (ttinst2.doc, 308)
Depth,
/// Copy an Indexed Element to the Top of the Stack (0x25) (ttinst2.doc, 309)
Cindex,
/// Move an Indexed Element to the Top of the Stack (0x26) (ttinst2.doc, 310)
Mindex,
/// Roll the Top Three Stack Elements (0x8a) (ttinst2.doc, 311)
Roll,
/// If Test (0x58) (ttinst2.doc, 313-314)
If,
/// Else (0x1b) (ttinst2.doc, 315)
Else,
/// End If (0x59) (ttinst2.doc, 316)
Eif,
/// Jump Relative on True (0x78) (ttinst2.doc, 317-318)
Jrot,
/// Jump (0x1c) (ttinst2.doc, 319)
Jmpr,
/// Jump Relative on False (0x79) (ttinst2.doc, 320-321)
Jrof,
/// Less Than (0x50) (ttinst2.doc, 323)
Lt,
/// Less Than or Equal (0x51) (ttinst2.doc, 324)
Lteq,
/// Greater Than (0x52) (ttinst2.doc, 325)
Gt,
/// Greater Than or Equal (0x53) (ttinst2.doc, 326)
Gteq,
/// Equal (0x54) (ttinst2.doc, 327)
Eq,
/// Not Equal (0x55) (ttinst2.doc, 328)
Neq,
/// Odd (0x56) (ttinst2.doc, 329)
Odd,
/// Even (0x57) (ttinst2.doc, 330)
Even,
/// Logical And (0x5a) (ttinst2.doc, 331-332)
And,
/// Logical Or (0x5b) (ttinst2.doc, 333)
Or,
/// Logical Not (0x5c) (ttinst2.doc, 334)
Not,
/// Add (0x60) (ttinst2.doc, 336)
Add,
/// Subtract (0x61) (ttinst2.doc, 337)
Sub,
/// Divide (0x62) (ttinst2.doc, 338)
Div,
/// Multiply (0x63) (ttinst2.doc, 339)
Mul,
/// Absolute Value (0x64) (ttinst2.doc, 340)
Abs,
/// Negate (0x65) (ttinst2.doc, 341)
Neg,
/// Floor (0x66) (ttinst2.doc, 342)
Floor,
/// Ceiling (0x67) (ttinst2.doc, 343)
Ceiling,
/// Maximum of Top Two Stack Elements (0x8b) (ttinst2.doc, 344)
Max,
/// Minimum of Top Two Stack Elements (0x8c) (ttinst2.doc, 345)
Min,
/// Round Value (0x68-0x6b) (ttinst2.doc, 347)
Round(DistanceType),
/// No Rounding of Value (0x6c-0x6f) (ttinst2.doc, 349)
Nround(DistanceType),
/// Function Definition (0x2c) (ttinst2.doc, 351)
Fdef,
/// End Function Definition (0x2d) (ttinst2.doc, 352)
Endf,
/// Call Function (0x2b) (ttinst2.doc, 353)
Call,
/// Loop and Call Function (0x2a) (ttinst2.doc, 354)
Loopcall,
/// Instruction Definition (0x89) (ttinst2.doc, 355)
Idef,
/// Debug Call (0x4f) (ttinst2.doc, 356)
Debug,
/// Get Information (0x88) (ttinst2.doc, 357-360)
Getinfo,
/// Get Variation (0x91) (ttinst2.doc, 361)
Getvariation,
}
impl<'a> Instruction<'a> {
#[inline]
pub fn parse<'b, 'c>(data: &'b [u8], pc: &'c mut usize)
-> Result<Instruction<'b>, HintingParseError> {
let op = try!(get(data, pc).ok_or(HintingParseError::Eof));
match op {
0x40 | 0xb0...0xb7 => {
let count = if op == 0x40 {
try!(get(data, pc).ok_or(HintingParseError::UnexpectedEof)) as usize
} else {
(op as usize & 7) + 1
};
if *pc + count <= data.len() {
let insn = Instruction::Pushb(&data[*pc..(*pc + count)]);
*pc += count;
Ok(insn)
} else {
Err(HintingParseError::UnexpectedEof)
}
}
0x41 | 0xb8...0xbf => {
let count = if op == 0x41 {
try!(get(data, pc).ok_or(HintingParseError::UnexpectedEof)) as usize
} else {
(op as usize & 7) + 1
};
if *pc + count * 2 <= data.len() {
let insn = Instruction::Pushw(&data[*pc..(*pc + count * 2)]);
*pc += count * 2;
Ok(insn)
} else {
Err(HintingParseError::UnexpectedEof)
}
}
0x43 => Ok(Instruction::Rs),
0x42 => Ok(Instruction::Ws),
0x44 => Ok(Instruction::Wcvtp),
0x70 => Ok(Instruction::Wcvtf),
0x45 => Ok(Instruction::Rcvt),
0x00 => Ok(Instruction::Svtca(Axis::Y)),
0x01 => Ok(Instruction::Svtca(Axis::X)),
0x02 => Ok(Instruction::Spvtca(Axis::Y)),
0x03 => Ok(Instruction::Spvtca(Axis::X)),
0x04 => Ok(Instruction::Sfvtca(Axis::Y)),
0x05 => Ok(Instruction::Sfvtca(Axis::X)),
0x06 => Ok(Instruction::Spvtl(LineOrientation::Parallel)),
0x07 => Ok(Instruction::Spvtl(LineOrientation::Perpendicular)),
0x08 => Ok(Instruction::Sfvtl(LineOrientation::Parallel)),
0x09 => Ok(Instruction::Sfvtl(LineOrientation::Perpendicular)),
0x0e => Ok(Instruction::Sfvtpv),
0x86 => Ok(Instruction::Sdpvtl(LineOrientation::Parallel)),
0x87 => Ok(Instruction::Sdpvtl(LineOrientation::Perpendicular)),
0x0a => Ok(Instruction::Spvfs),
0x0b => Ok(Instruction::Sfvfs),
0x0c => Ok(Instruction::Gpv),
0x0d => Ok(Instruction::Gfv),
0x10 => Ok(Instruction::Srp0),
0x11 => Ok(Instruction::Srp1),
0x12 => Ok(Instruction::Srp2),
0x13 => Ok(Instruction::Szp0),
0x14 => Ok(Instruction::Szp1),
0x15 => Ok(Instruction::Szp2),
0x16 => Ok(Instruction::Szps),
0x19 => Ok(Instruction::Rthg),
0x18 => Ok(Instruction::Rtg),
0x3d => Ok(Instruction::Rtdg),
0x7d => Ok(Instruction::Rdtg),
0x7c => Ok(Instruction::Rutg),
0x7a => Ok(Instruction::Roff),
0x76 => Ok(Instruction::Sround),
0x77 => Ok(Instruction::S45round),
0x17 => Ok(Instruction::Sloop),
0x1a => Ok(Instruction::Smd),
0x8e => Ok(Instruction::Instctrl),
0x85 => Ok(Instruction::Scanctrl),
0x8d => Ok(Instruction::Scantype),
0x1d => Ok(Instruction::Scvtci),
0x1e => Ok(Instruction::Sswci),
0x1f => Ok(Instruction::Ssw),
0x4d => Ok(Instruction::Flipon),
0x4e => Ok(Instruction::Flipoff),
0x7e => Ok(Instruction::Sangw),
0x5e => Ok(Instruction::Sdb),
0x5f => Ok(Instruction::Sds),
0x46 => Ok(Instruction::Gc(WhichPosition::Current)),
0x47 => Ok(Instruction::Gc(WhichPosition::Original)),
0x48 => Ok(Instruction::Scfs),
0x49 => Ok(Instruction::Md(WhichPosition::Current)),
0x4a => Ok(Instruction::Md(WhichPosition::Original)),
0x4b => Ok(Instruction::Mppem),
0x4c => Ok(Instruction::Mps),
0x80 => Ok(Instruction::Flippt),
0x81 => Ok(Instruction::Fliprgon),
0x82 => Ok(Instruction::Fliprgoff),
0x32 => Ok(Instruction::Shp(ZonePoint::Zone1Point2)),
0x33 => Ok(Instruction::Shp(ZonePoint::Zone0Point1)),
0x34 => Ok(Instruction::Shc(ZonePoint::Zone1Point2)),
0x35 => Ok(Instruction::Shc(ZonePoint::Zone0Point1)),
0x36 => Ok(Instruction::Shz(ZonePoint::Zone1Point2)),
0x37 => Ok(Instruction::Shz(ZonePoint::Zone0Point1)),
0x38 => Ok(Instruction::Shpix),
0x3a | 0x3b => Ok(Instruction::Msirp(SetRP0(op == 0x3b))),
0x2e | 0x2f => Ok(Instruction::Mdap(ShouldRound(op == 0x2f))),
0x3e | 0x3f => Ok(Instruction::Miap(ShouldRound(op == 0x3f))),
0xc0...0xdf => {
Ok(Instruction::Mdrp(SetRP0((op & 0b10000) != 0),
ApplyMinimumDistance((op & 0b01000) != 0),
ShouldRound((op & 0b00100) != 0),
try!(DistanceType::parse(op & 0b00011))))
}
0xe0...0xff => {
Ok(Instruction::Mirp(SetRP0((op & 0b10000) != 0),
ApplyMinimumDistance((op & 0b01000) != 0),
ShouldRound((op & 0b00100) != 0),
try!(DistanceType::parse(op & 0b00011))))
}
0x3c => Ok(Instruction::Alignrp),
0x0f => Ok(Instruction::Isect),
0x27 => Ok(Instruction::Alignpts),
0x39 => Ok(Instruction::Ip),
0x29 => Ok(Instruction::Utp),
0x30 => Ok(Instruction::Iup(Axis::Y)),
0x31 => Ok(Instruction::Iup(Axis::X)),
0x5d => Ok(Instruction::Deltap1),
0x71 => Ok(Instruction::Deltap2),
0x72 => Ok(Instruction::Deltap3),
0x73 => Ok(Instruction::Deltac1),
0x74 => Ok(Instruction::Deltac2),
0x75 => Ok(Instruction::Deltac3),
0x20 => Ok(Instruction::Dup),
0x21 => Ok(Instruction::Pop),
0x22 => Ok(Instruction::Clear),
0x23 => Ok(Instruction::Swap),
0x24 => Ok(Instruction::Depth),
0x25 => Ok(Instruction::Cindex),
0x26 => Ok(Instruction::Mindex),
0x8a => Ok(Instruction::Roll),
0x58 => Ok(Instruction::If),
0x1b => Ok(Instruction::Else),
0x59 => Ok(Instruction::Eif),
0x78 => Ok(Instruction::Jrot),
0x1c => Ok(Instruction::Jmpr),
0x79 => Ok(Instruction::Jrof),
0x50 => Ok(Instruction::Lt),
0x51 => Ok(Instruction::Lteq),
0x52 => Ok(Instruction::Gt),
0x53 => Ok(Instruction::Gteq),
0x54 => Ok(Instruction::Eq),
0x55 => Ok(Instruction::Neq),
0x56 => Ok(Instruction::Odd),
0x57 => Ok(Instruction::Even),
0x5a => Ok(Instruction::And),
0x5b => Ok(Instruction::Or),
0x5c => Ok(Instruction::Not),
0x60 => Ok(Instruction::Add),
0x61 => Ok(Instruction::Sub),
0x62 => Ok(Instruction::Div),
0x63 => Ok(Instruction::Mul),
0x64 => Ok(Instruction::Abs),
0x65 => Ok(Instruction::Neg),
0x66 => Ok(Instruction::Floor),
0x67 => Ok(Instruction::Ceiling),
0x8b => Ok(Instruction::Max),
0x8c => Ok(Instruction::Min),
0x68...0x6b => Ok(Instruction::Round(try!(DistanceType::parse(op & 0b11)))),
0x6c...0x6f => Ok(Instruction::Nround(try!(DistanceType::parse(op & 0b11)))),
0x2c => Ok(Instruction::Fdef),
0x2d => Ok(Instruction::Endf),
0x2b => Ok(Instruction::Call),
0x2a => Ok(Instruction::Loopcall),
0x89 => Ok(Instruction::Idef),
0x4f => Ok(Instruction::Debug),
0x88 => Ok(Instruction::Getinfo),
0x91 => Ok(Instruction::Getvariation),
_ => Err(HintingParseError::UnknownOpcode(op)),
}
}
}
fn get(data: &[u8], pc: &mut usize) -> Option<u8> {
match data.get(*pc) {
Some(&byte) => {
*pc += 1;
Some(byte)
}
None => None,
}
}
#[derive(Copy, Clone, Debug)]
#[repr(u8)]
pub enum Axis {
Y = 0,
X = 1,
}
impl Axis {
#[inline]
pub fn as_point(self) -> Point2D<F2Dot14> {
match self {
Axis::Y => Point2D::new(F2DOT14_ZERO, F2DOT14_ONE),
Axis::X => Point2D::new(F2DOT14_ONE, F2DOT14_ZERO),
}
}
}
#[derive(Copy, Clone, Debug)]
#[repr(u8)]
pub enum LineOrientation {
Parallel = 0,
Perpendicular = 1,
}
#[derive(Copy, Clone, Debug)]
#[repr(u8)]
pub enum WhichPosition {
// Use the current position.
Current = 0,
// Use the position in the original outline.
Original = 1,
}
#[derive(Copy, Clone, PartialEq, Debug)]
#[repr(u8)]
pub enum ZonePoint {
Zone1Point2 = 0,
Zone0Point1 = 1,
}
#[derive(Copy, Clone, PartialEq, Debug)]
pub struct SetRP0(pub bool);
#[derive(Copy, Clone, PartialEq, Debug)]
pub struct ApplyMinimumDistance(pub bool);
#[derive(Copy, Clone, PartialEq, Debug)]
pub struct ShouldRound(pub bool);
// See `MDRP` (ttinst2.doc, 277)
#[derive(Copy, Clone, PartialEq, Debug)]
#[repr(u8)]
pub enum DistanceType {
Gray = 0,
Black = 1,
White = 2,
}
impl DistanceType {
fn parse(value: u8) -> Result<DistanceType, HintingParseError> {
match value {
0 => Ok(DistanceType::Gray),
1 => Ok(DistanceType::Black),
2 => Ok(DistanceType::White),
_ => Err(HintingParseError::InvalidDistanceType),
}
}
}

View File

@ -1,422 +0,0 @@
// Copyright 2017 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! The TrueType interpreter.
use byteorder::{BigEndian, ByteOrder};
use error::{HintingAnalysisError, HintingExecutionError, HintingParseError};
use hinting::insns::Instruction;
use hinting::{FONT_SMOOTHING_GRAYSCALE, GETINFO_VERSION, Hinter};
use hinting::{INFO_RESULT_FONT_SMOOTHING_GRAYSCALE_SHIFT, InfoSelector, RoundState, VERSION};
use num_traits::Zero;
use std::cmp;
use util::{F26DOT6_ZERO, F26Dot6};
impl<'a> Hinter<'a> {
pub fn exec(&mut self) -> Result<(), HintingExecutionError> {
loop {
// Fetch the current frame.
let frame = match self.call_stack.last() {
None => return Ok(()),
Some(&frame) if frame.pc == frame.end => {
self.call_stack.pop();
continue
}
Some(&frame) => frame,
};
// Decode the next instruction, and advance the program counter.
let mut new_pc = frame.pc;
let bytecode = self.scripts[frame.script].bytecode;
let instruction =
try!(Instruction::parse(bytecode,
&mut new_pc).map_err(HintingExecutionError::ParseError));
// Execute it.
match instruction {
Instruction::Pushb(bytes) => {
self.stack.extend(bytes.iter().map(|&b| b as i32))
}
Instruction::Pushw(bytes) => {
self.stack.extend(bytes.chunks(2).map(|bs| BigEndian::read_i16(bs) as i32))
}
Instruction::Rs => {
// We should throw an exception here if the storage area isn't big enough, but
// let's follow Postel's law.
let addr = try!(self.pop()) as usize;
match self.storage_area.get(addr) {
Some(&value) => self.stack.push(value),
None => self.stack.push(0),
}
}
Instruction::Ws => {
// We should throw an exception here if the storage area isn't big enough, but
// let's follow Postel's law.
//
// FIXME(pcwalton): Cap the size of the storage area?
let (value, addr) = (try!(self.pop()), try!(self.pop()) as usize);
if self.storage_area.len() < addr + 1 {
self.storage_area.resize(addr + 1, 0)
}
self.storage_area[addr] = value
}
Instruction::Rcvt => {
let addr = try!(self.pop()) as usize;
let value = *try!(self.control_value_table
.get(addr)
.ok_or(HintingExecutionError::CvtReadOutOfBounds));
self.stack.push(value.0)
}
Instruction::Svtca(axis) => {
self.projection_vector = axis.as_point();
self.freedom_vector = axis.as_point();
}
Instruction::Spvtca(axis) => self.projection_vector = axis.as_point(),
Instruction::Sfvtca(axis) => self.freedom_vector = axis.as_point(),
Instruction::Srp0 => self.reference_points[0] = try!(self.pop()) as u32,
Instruction::Srp1 => self.reference_points[1] = try!(self.pop()) as u32,
Instruction::Srp2 => self.reference_points[2] = try!(self.pop()) as u32,
Instruction::Szp0 => self.zone_points[0] = try!(self.pop()) as u32,
Instruction::Szp1 => self.zone_points[1] = try!(self.pop()) as u32,
Instruction::Szp2 => self.zone_points[2] = try!(self.pop()) as u32,
Instruction::Szps => {
let zone = try!(self.pop()) as u32;
self.zone_points = [zone; 3]
}
Instruction::Rthg => self.round_state = RoundState::RoundToHalfGrid,
Instruction::Rtg => self.round_state = RoundState::RoundToGrid,
Instruction::Rtdg => self.round_state = RoundState::RoundToDoubleGrid,
Instruction::Rutg => self.round_state = RoundState::RoundUpToGrid,
Instruction::Roff => self.round_state = RoundState::RoundOff,
Instruction::Sround => {
// TODO(pcwalton): Super rounding.
try!(self.pop());
}
Instruction::Scanctrl | Instruction::Scantype => {
// Not applicable to antialiased glyphs.
try!(self.pop());
}
Instruction::Mppem => {
// We always scale both axes in the same direction, so we don't have to look
// at the projection vector.
self.stack.push(self.point_size.round() as i32)
}
Instruction::Dup => {
let value = *try!(self.stack
.last()
.ok_or(HintingExecutionError::StackUnderflow));
self.stack.push(value);
}
Instruction::Pop => {
try!(self.pop());
}
Instruction::Clear => self.stack.clear(),
Instruction::Swap => {
let (a, b) = (try!(self.pop()), try!(self.pop()));
self.stack.push(a);
self.stack.push(b);
}
Instruction::Mindex => {
let index = try!(self.pop()) as usize;
if index >= self.stack.len() {
return Err(HintingExecutionError::StackUnderflow)
}
let rindex = self.stack.len() - 1 - index;
let value = self.stack.remove(rindex);
self.stack.push(value)
}
Instruction::If => {
let cond = try!(self.pop());
if cond == 0 {
// Move to the instruction following `else` or `eif`.
let else_target_index = self.scripts[frame.script]
.branch_targets
.binary_search_by(|script| {
script.branch_location.cmp(&frame.pc)
}).unwrap();
new_pc = self.scripts[frame.script]
.branch_targets[else_target_index]
.target_location + 1
}
}
Instruction::Else => {
// The only way we get here is by falling off the end of a then-branch. So jump
// to the instruction following the matching `eif`.
let eif_target_index = self.scripts[frame.script]
.branch_targets
.binary_search_by(|script| {
script.branch_location.cmp(&frame.pc)
}).unwrap();
new_pc = self.scripts[frame.script]
.branch_targets[eif_target_index]
.target_location + 1
}
Instruction::Eif => {
// Likewise, the only way we get here is by falling off the end of a
// then-branch.
}
Instruction::Lt => {
let (rhs, lhs) = (try!(self.pop()), try!(self.pop()));
self.stack.push((lhs < rhs) as i32)
}
Instruction::Lteq => {
let (rhs, lhs) = (try!(self.pop()), try!(self.pop()));
self.stack.push((lhs <= rhs) as i32)
}
Instruction::Gt => {
let (rhs, lhs) = (try!(self.pop()), try!(self.pop()));
self.stack.push((lhs > rhs) as i32)
}
Instruction::Gteq => {
let (rhs, lhs) = (try!(self.pop()), try!(self.pop()));
self.stack.push((lhs >= rhs) as i32)
}
Instruction::Eq => {
let (rhs, lhs) = (try!(self.pop()), try!(self.pop()));
self.stack.push((lhs == rhs) as i32)
}
Instruction::Neq => {
let (rhs, lhs) = (try!(self.pop()), try!(self.pop()));
self.stack.push((lhs != rhs) as i32)
}
Instruction::And => {
let (rhs, lhs) = (try!(self.pop()), try!(self.pop()));
self.stack.push((lhs != 0 && rhs != 0) as i32)
}
Instruction::Or => {
let (rhs, lhs) = (try!(self.pop()), try!(self.pop()));
self.stack.push((lhs != 0 || rhs != 0) as i32)
}
Instruction::Not => {
let cond = try!(self.pop());
self.stack.push((cond == 0) as i32)
}
Instruction::Add => {
let (rhs, lhs) = (F26Dot6(try!(self.pop())), F26Dot6(try!(self.pop())));
self.stack.push((lhs + rhs).0)
}
Instruction::Sub => {
let (rhs, lhs) = (F26Dot6(try!(self.pop())), F26Dot6(try!(self.pop())));
self.stack.push((lhs - rhs).0)
}
Instruction::Div => {
let (rhs, lhs) = (F26Dot6(try!(self.pop())), F26Dot6(try!(self.pop())));
if rhs.is_zero() {
// Obey Postel's law…
self.stack.push(F26DOT6_ZERO.0)
} else {
self.stack.push((lhs / rhs).0)
}
}
Instruction::Mul => {
let (rhs, lhs) = (F26Dot6(try!(self.pop())), F26Dot6(try!(self.pop())));
self.stack.push((lhs * rhs).0)
}
Instruction::Abs => {
// Actually in fixed point, but it works out the same way.
let n = try!(self.pop());
self.stack.push(n.abs())
}
Instruction::Neg => {
let n = F26Dot6(try!(self.pop()));
self.stack.push((-n).0)
}
Instruction::Max => {
let (rhs, lhs) = (F26Dot6(try!(self.pop())), F26Dot6(try!(self.pop())));
self.stack.push(cmp::max(rhs, lhs).0)
}
Instruction::Min => {
let (rhs, lhs) = (F26Dot6(try!(self.pop())), F26Dot6(try!(self.pop())));
self.stack.push(cmp::max(rhs, lhs).0)
}
Instruction::Fdef => {
// We should throw an exception here if the function definition list isn't big
// enough, but let's follow Postel's law.
//
// FIXME(pcwalton): Cap the size of the function definitions?
let id = try!(self.pop()) as usize;
if self.functions.len() < id + 1 {
self.functions.resize(id + 1, None)
}
let branch_target_index = self.scripts[frame.script]
.branch_targets
.binary_search_by(|script| {
script.branch_location.cmp(&frame.pc)
}).unwrap();
let end_pc = self.scripts[frame.script]
.branch_targets[branch_target_index]
.target_location;
self.functions[id] = Some(Frame::new(new_pc, end_pc, frame.script));
new_pc = end_pc + 1
}
Instruction::Call => {
let id = try!(self.pop()) as usize;
let new_frame = match self.functions.get(id) {
Some(&Some(new_frame)) => new_frame,
Some(&None) | None => {
return Err(HintingExecutionError::CallToUndefinedFunction)
}
};
// Save our return address.
self.call_stack.last_mut().unwrap().pc = new_pc;
// Jump to the new function.
self.call_stack.push(new_frame);
new_pc = new_frame.pc
}
Instruction::Getinfo => {
let selector = InfoSelector::from_bits_truncate(try!(self.pop()));
// We only handle a subset of the selectors here.
//
// TODO(pcwalton): Handle the ones relating to subpixel AA.
let mut result = 0;
if selector.contains(VERSION) {
result |= GETINFO_VERSION
}
if selector.contains(FONT_SMOOTHING_GRAYSCALE) {
result |= 1 << INFO_RESULT_FONT_SMOOTHING_GRAYSCALE_SHIFT
}
self.stack.push(result)
}
_ => {
println!("TODO: {:?}", instruction);
}
}
// Advance the program counter.
self.call_stack.last_mut().unwrap().pc = new_pc;
}
}
#[inline]
fn pop(&mut self) -> Result<i32, HintingExecutionError> {
self.stack.pop().ok_or(HintingExecutionError::StackUnderflow)
}
}
pub struct Script<'a> {
bytecode: &'a [u8],
branch_targets: Vec<BranchTarget>,
}
impl<'a> Script<'a> {
pub fn new<'b>(bytecode: &'b [u8]) -> Result<Script<'b>, HintingAnalysisError> {
let mut interpreter = Script {
bytecode: bytecode,
branch_targets: vec![],
};
try!(interpreter.populate_branch_targets());
Ok(interpreter)
}
#[inline]
pub fn len(&self) -> usize {
self.bytecode.len()
}
// This is a little bit tricky because we have to maintain sorted order of the `branch_targets`
// array for future binary searches.
fn populate_branch_targets(&mut self) -> Result<(), HintingAnalysisError> {
let (mut pc, mut pending_branch_targets) = (0, vec![]);
loop {
let location = pc;
let instruction = match Instruction::parse(self.bytecode, &mut pc) {
Ok(instruction) => instruction,
Err(HintingParseError::Eof) => break,
Err(err) => return Err(HintingAnalysisError::ParseError(err)),
};
match instruction {
Instruction::If | Instruction::Fdef | Instruction::Idef => {
pending_branch_targets.push((self.branch_targets.len(), instruction));
self.branch_targets.push(BranchTarget {
branch_location: location,
target_location: 0,
});
}
Instruction::Endf => {
let (index, branch_instruction) = try!(pending_branch_targets.pop().ok_or(
HintingAnalysisError::BranchTargetMissingBranch));
match branch_instruction {
Instruction::Fdef | Instruction::Idef => {
self.branch_targets[index].target_location = location
}
_ => return Err(HintingAnalysisError::MismatchedBranchInstruction),
}
}
Instruction::Eif => {
let (index, branch_instruction) = try!(pending_branch_targets.pop().ok_or(
HintingAnalysisError::BranchTargetMissingBranch));
match branch_instruction {
Instruction::If | Instruction::Else => {
self.branch_targets[index].target_location = location
}
_ => return Err(HintingAnalysisError::MismatchedBranchInstruction),
}
}
Instruction::Else => {
let (index, branch_instruction) = try!(pending_branch_targets.pop().ok_or(
HintingAnalysisError::BranchTargetMissingBranch));
match branch_instruction {
Instruction::If => {
self.branch_targets[index].target_location = location;
pending_branch_targets.push((self.branch_targets.len(), instruction));
self.branch_targets.push(BranchTarget {
branch_location: location,
target_location: 0,
});
}
_ => return Err(HintingAnalysisError::MismatchedBranchInstruction),
}
}
_ => {}
}
}
if pending_branch_targets.is_empty() {
Ok(())
} else {
Err(HintingAnalysisError::BranchMissingBranchTarget)
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct Frame {
/// The current program counter.
pc: usize,
/// The PC at which to stop execution.
end: usize,
/// The index of the script.
script: usize,
}
impl Frame {
pub fn new(pc: usize, end: usize, script_index: usize) -> Frame {
Frame {
pc: pc,
end: end,
script: script_index,
}
}
}
#[derive(Clone, Copy, Debug)]
struct BranchTarget {
branch_location: usize,
target_location: usize,
}

View File

@ -1,221 +0,0 @@
// Copyright 2017 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![allow(dead_code)]
//! The TrueType hinting VM.
//!
//! See: https://www.microsoft.com/typography/otspec/ttinst.htm
use byteorder::{BigEndian, ByteOrder};
use error::{HinterCreationError, HintingExecutionError};
use euclid::Point2D;
use font::Font;
use hinting::interp::{Frame, Script};
use util::{F26Dot6, F2Dot14};
mod insns;
mod interp;
/// The version we return for the `getinfo` instruction. FreeType uses 35, so we do as well. (This
/// supposedly corresponds to Windows 98.)
pub const GETINFO_VERSION: i32 = 35;
const FONT_PROGRAM: usize = 0;
const CONTROL_VALUE_PROGRAM: usize = 1;
/// A TrueType hinting virtual machine.
pub struct Hinter<'a> {
// Scripts that we've analyzed so far.
scripts: Vec<Script<'a>>,
// The VM's evaluation stack.
stack: Vec<i32>,
// The VM's call stack.
call_stack: Vec<Frame>,
// The set of defined functions.
functions: Vec<Option<Frame>>,
// The Control Value Table: the VM's initialized memory.
control_value_table: Vec<F26Dot6>,
// The Storage Area: the VM's uninitialized memory.
storage_area: Vec<i32>,
// The current font size.
point_size: f32,
// The projection vector, in 2.14 fixed point.
projection_vector: Point2D<F2Dot14>,
// The dual projection vector, in 2.14 fixed point.
dual_projection_vector: Point2D<F2Dot14>,
// The freedom vector, in 2.14 fixed point.
freedom_vector: Point2D<F2Dot14>,
// The reference point indices.
reference_points: [u32; 3],
// The zone numbers.
zone_points: [u32; 3],
// The round state.
round_state: RoundState,
// The loop variable count.
loop_count: u32,
// The minimum distance value.
minimum_distance: u32,
// Instruction control flags.
instruction_control: InstructionControl,
// Threshold value for ppem. See `SCANCTRL` (ttinst1.doc, 244-245).
dropout_threshold: u8,
// Special dropout control.
dropout_control: DropoutControl,
// The scan type. See `SCANTYPE` (ttinst1.doc, 246-247).
scan_type: ScanType,
// The control value cut in. See `SCVTSI` (ttinst1.doc, 249).
control_value_cut_in: u32,
// The single width cut in. See `SSWCI` (ttinst1.doc, 250).
single_width_cut_in: u32,
// The single width value. See `SSW` (ttinst1.doc, 251).
single_width_value: i32,
// The angle weight. Per spec, does nothing. See `SANGW` (ttinst1.doc, 254).
angle_weight: u32,
// The delta base. See `SDB` (ttinst1.doc, 255).
delta_base: u32,
// The delta shift. See `SDS` (ttinst1.doc, 256).
delta_shift: u32,
// Various graphics state flags.
graphics_state_flags: GraphicsStateFlags,
}
impl<'a> Hinter<'a> {
pub fn new<'b>(font: &'b Font) -> Result<Hinter<'b>, HinterCreationError> {
let font_program = font.font_program();
let control_value_program = font.control_value_program();
let scripts = vec![
try!(Script::new(font_program).map_err(HinterCreationError::FontProgramAnalysisError)),
try!(Script::new(control_value_program).map_err(
HinterCreationError::ControlValueProgramAnalysisError)),
];
let cvt = font.control_value_table().chunks(2).map(|bytes| {
// FIXME(pcwalton): This is wrong!
let unscaled = BigEndian::read_i16(bytes) as i32;
F26Dot6(unscaled)
}).collect();
// Initialize the call stack to the font program, so that we'll start executing it.
let call_stack = vec![Frame::new(0, scripts[FONT_PROGRAM].len(), FONT_PROGRAM)];
let mut hinter = Hinter {
scripts: scripts,
stack: vec![],
call_stack: call_stack,
functions: vec![],
control_value_table: cvt,
storage_area: vec![],
point_size: 0.0,
projection_vector: Point2D::zero(),
dual_projection_vector: Point2D::zero(),
freedom_vector: Point2D::zero(),
reference_points: [0; 3],
zone_points: [0; 3],
round_state: RoundState::RoundToHalfGrid,
loop_count: 0,
minimum_distance: 0,
instruction_control: InstructionControl::empty(),
dropout_threshold: 0,
dropout_control: DropoutControl::empty(),
scan_type: ScanType::SimpleDropoutControlIncludingStubs,
control_value_cut_in: 0,
single_width_cut_in: 0,
single_width_value: 0,
angle_weight: 0,
delta_base: 0,
delta_shift: 0,
graphics_state_flags: AUTO_FLIP,
};
try!(hinter.exec().map_err(HinterCreationError::FontProgramExecutionError));
Ok(hinter)
}
/// Sets the point size and reevaluates the control value program (`prep`).
pub fn set_point_size(&mut self, new_point_size: f32) -> Result<(), HintingExecutionError> {
self.point_size = new_point_size;
self.call_stack.push(Frame::new(0,
self.scripts[CONTROL_VALUE_PROGRAM].len(),
CONTROL_VALUE_PROGRAM));
self.exec()
}
}
#[derive(Copy, Clone, Debug)]
#[repr(u8)]
enum RoundState {
RoundToHalfGrid = 0,
RoundToGrid = 1,
RoundToDoubleGrid = 2,
RoundDownToGrid = 3,
RoundUpToGrid = 4,
RoundOff = 5,
}
#[derive(Copy, Clone, PartialEq, Debug)]
#[repr(u8)]
pub enum ScanType {
SimpleDropoutControlIncludingStubs = 0,
SimpleDropoutControlExcludingStubs = 1,
NoDropoutControl = 2,
SmartDropoutControlIncludingStubs = 3,
SmartDropoutControlExcludingStubs = 4,
}
bitflags! {
pub flags InstructionControl: u8 {
const INHIBIT_GRID_FITTING = 1 << 0,
const IGNORE_CVT_PARAMETERS = 1 << 1,
const NATIVE_SUBPIXEL_AA = 1 << 2,
}
}
bitflags! {
pub flags DropoutControl: u8 {
const DROPOUT_IF_PPEM_LESS_THAN_THRESHOLD = 1 << 0,
const DROPOUT_IF_ROTATED = 1 << 1,
const DROPOUT_IF_STRETCHED = 1 << 2,
const NO_DROPOUT_IF_PPEM_GREATER_THAN_THRESHOLD = 1 << 3,
const NO_DROPOUT_IF_UNROTATED = 1 << 4,
const NO_DROPOUT_IF_UNSTRETCHED = 1 << 5,
}
}
bitflags! {
flags GraphicsStateFlags: u8 {
// See `FLIPON` (default true) (ttinst1.doc, 252).
const AUTO_FLIP = 1 << 1,
}
}
bitflags! {
// Info returned by the `getinfo` instruction.
pub flags InfoSelector: i32 {
const VERSION = 0x1,
const GLYPH_ROTATION = 0x2,
const GLYPH_STRETCHED = 0x4,
const FONT_VARIATIONS = 0x8,
const VERTICAL_PHANTOM_POINTS = 0x10,
const FONT_SMOOTHING_GRAYSCALE = 0x20,
const SUBPIXEL_AA_ENABLED = 0x40,
const SUBPIXEL_AA_COMPATIBLE_WIDTHS_ENABLED = 0x80,
const SUBPIXEL_AA_HORIZONTAL_LCD_STRIPE_ORIENTATION = 0x100,
const SUBPIXEL_AA_BGR_LCD_STRIPE_ORDER = 0x200,
const SUBPIXEL_AA_SUBPIXEL_POSITIONED_TEXT_ENABLED = 0x400,
const SUBPIXEL_AA_SYMMETRIC_RENDERING_ENABLED = 0x800,
const SUBPIXEL_AA_GRAY_RENDERING_ENABLED = 0x1000,
}
}
pub const INFO_RESULT_VERSION_SHIFT: i32 = 0;
pub const INFO_RESULT_FONT_SMOOTHING_GRAYSCALE_SHIFT: i32 = 12;

View File

@ -1,110 +0,0 @@
// Copyright 2017 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! A high-performance GPU rasterizer for OpenType fonts.
//!
//! ## Introduction
//!
//! Pathfinder is a fast, practical GPU-based rasterizer for OpenType fonts using OpenGL 4.3. It
//! features:
//!
//! * Very low setup time. Glyph outlines can go from the `.otf` file to the GPU in a form ready
//! for rasterization in less than a microsecond. There is no expensive tessellation or
//! preprocessing step.
//!
//! * High quality antialiasing. Unlike techniques that rely on multisample antialiasing,
//! Pathfinder computes exact fractional trapezoidal area coverage on a per-pixel basis.
//!
//! * Fast rendering, even at small pixel sizes. On typical systems, Pathfinder should easily
//! exceed the performance of the best CPU rasterizers.
//!
//! * Low memory consumption. The only memory overhead over the glyph and outline storage itself is
//! that of a coverage buffer which typically consumes somewhere between 4MB-16MB and can be
//! discarded under memory pressure. Outlines are stored on-GPU in a compressed format and usually
//! take up only a few dozen kilobytes.
//!
//! * Portability to most GPUs manufactured in the last few years, including integrated GPUs.
//!
//! ## Usage
//!
//! See `examples/generate-atlas.rs` for a simple example.
//!
//! Typically, the steps to use Pathfinder are:
//!
//! 1. Create a `Rasterizer` object. This holds the OpenGL state.
//!
//! 2. Open the font from disk (or elsewhere), and call `Font::new()` (or
//! `Font::from_collection_index` in the case of a `.ttc` or `.dfont` collection) to load it.
//!
//! 3. If the text to be rendered is not already shaped, call
//! `Font::glyph_mapping_for_codepoint_ranges()` to determine the glyphs needed to render the
//! text, and call `shaper::shape_text()` to convert the text to glyph IDs.
//!
//! 4. Create an `OutlineBuilder` and call `OutlineBuilder::add_glyph()` on each glyph to parse
//! each outline from the font. Then upload the outlines to the GPU with
//! `OutlineBuilder::create_buffers()`.
//!
//! 5. Create an `AtlasBuilder` with a suitable width (1024 or 2048 is usually fine) and call
//! `AtlasBuilder::pack_glyph()` on each glyph you need to render. Then call
//! `AtlasBuilder::create_atlas()` to upload the atlas buffer to the GPU.
//!
//! 6. Make a `CoverageBuffer` of an appropriate size (1024 or 2048 pixels on each side is
//! typically reasonable).
//!
//! 7. Create an image to render the atlas to with `Rasterizer::device().create_image()`. The
//! format should be `R8` and the buffer should be created read-write.
//!
//! 8. Draw the glyphs with `Rasterizer::draw_atlas()`.
//!
//! Don't forget to flush the queue (`Rasterizer::queue().flush()`) and/or perform appropriate
//! synchronization (`glMemoryBarrier()`) as necessary.
//!
//! ## Hardware requirements
//!
//! Pathfinder requires at least OpenGL 3.3 and either OpenGL 4.3 compute shader or OpenCL 1.2.
//! Intel GPUs in Sandy Bridge processors or later should be OK.
#![cfg_attr(test, feature(test))]
#[macro_use]
extern crate bitflags;
extern crate byteorder;
extern crate compute_shader;
extern crate euclid;
extern crate flate2;
extern crate gl;
#[cfg(test)]
extern crate memmap;
extern crate num_traits;
#[cfg(test)]
#[macro_use]
extern crate quickcheck;
#[cfg(test)]
extern crate test;
pub mod atlas;
pub mod charmap;
pub mod coverage;
pub mod error;
pub mod font;
pub mod hinting;
pub mod outline;
pub mod rasterizer;
pub mod shaper;
pub mod typesetter;
mod containers;
mod rect_packer;
mod tables;
mod util;
#[cfg(test)]
mod tests;

View File

@ -1,431 +0,0 @@
// Copyright 2017 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! Glyph vectors, uploaded in a resolution-independent manner to the GPU.
use error::{FontError, GlError};
use euclid::{Point2D, Size2D};
use font::{Font, PointKind};
use gl::types::{GLsizeiptr, GLuint};
use gl;
use std::mem;
use std::os::raw::c_void;
static DUMMY_VERTEX: Vertex = Vertex {
x: 0,
y: 0,
glyph_index: 0,
};
/// Packs up outlines for glyphs into a format that the GPU can process.
pub struct OutlineBuilder {
vertices: Vec<Vertex>,
indices: Vec<u32>,
descriptors: Vec<GlyphDescriptor>,
}
impl OutlineBuilder {
/// Creates a new empty set of outlines.
#[inline]
pub fn new() -> OutlineBuilder {
OutlineBuilder {
vertices: vec![DUMMY_VERTEX],
indices: vec![],
descriptors: vec![],
}
}
/// Begins a new path.
pub fn create_path(&mut self) -> PathBuilder {
let vertex_count = self.vertices.len();
let index_count = self.indices.len();
let descriptor_count = self.descriptors.len();
PathBuilder {
outline_builder: self,
vbo_start_index: vertex_count as u32,
vbo_end_index: vertex_count as u32,
ibo_start_index: index_count as u32,
point_index_in_path: 0,
glyph_index: descriptor_count as u16,
}
}
/// Adds a new glyph to the outline builder. Returns the glyph index, which is useful for later
/// calls to `Atlas::pack_glyph()`.
pub fn add_glyph(&mut self, font: &Font, glyph_id: u16) -> Result<u16, FontError> {
let glyph_index = self.descriptors.len() as u16;
let mut last_point_kind = PointKind::OnCurve;
let mut control_point_index = 0;
let mut control_points = [Point2D::zero(), Point2D::zero(), Point2D::zero()];
let mut path_builder = self.create_path();
try!(font.for_each_point(glyph_id, |point| {
control_points[control_point_index] = point.position;
control_point_index += 1;
if point.index_in_contour == 0 {
path_builder.move_to(&control_points[0]);
control_point_index = 0
} else if point.kind == PointKind::OnCurve {
match last_point_kind {
PointKind::FirstCubicControl => {}
PointKind::SecondCubicControl => {
path_builder.cubic_curve_to(&control_points[0],
&control_points[1],
&control_points[2])
}
PointKind::QuadControl => {
path_builder.quad_curve_to(&control_points[0], &control_points[1])
}
PointKind::OnCurve => path_builder.line_to(&control_points[0]),
}
control_point_index = 0
}
last_point_kind = point.kind
}));
let bounds = try!(font.glyph_bounds(glyph_id));
path_builder.finish(&bounds, font.units_per_em() as u32, glyph_id);
Ok(glyph_index)
}
/// Uploads the outlines to the GPU.
pub fn create_buffers(self) -> Result<Outlines, GlError> {
// TODO(pcwalton): Try using `glMapBuffer` here. Requires precomputing contour types and
// counts.
unsafe {
let (mut vertices, mut indices, mut descriptors) = (0, 0, 0);
gl::GenBuffers(1, &mut vertices);
gl::GenBuffers(1, &mut indices);
gl::GenBuffers(1, &mut descriptors);
gl::BindBuffer(gl::ARRAY_BUFFER, vertices);
gl::BufferData(gl::ARRAY_BUFFER,
(self.vertices.len() * mem::size_of::<Vertex>()) as GLsizeiptr,
self.vertices.as_ptr() as *const Vertex as *const c_void,
gl::STATIC_DRAW);
gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, indices);
gl::BufferData(gl::ELEMENT_ARRAY_BUFFER,
(self.indices.len() * mem::size_of::<u32>()) as GLsizeiptr,
self.indices.as_ptr() as *const u32 as *const c_void,
gl::STATIC_DRAW);
let length = self.descriptors.len() * mem::size_of::<GlyphDescriptor>();
gl::BindBuffer(gl::UNIFORM_BUFFER, descriptors);
gl::BufferData(gl::UNIFORM_BUFFER,
length as GLsizeiptr,
self.descriptors.as_ptr() as *const GlyphDescriptor as *const c_void,
gl::STATIC_DRAW);
Ok(Outlines {
vertices_buffer: vertices,
indices_buffer: indices,
descriptors_buffer: descriptors,
descriptors: self.descriptors,
indices_count: self.indices.len(),
})
}
}
}
/// Resolution-independent glyph vectors uploaded to the GPU.
pub struct Outlines {
vertices_buffer: GLuint,
indices_buffer: GLuint,
descriptors_buffer: GLuint,
descriptors: Vec<GlyphDescriptor>,
indices_count: usize,
}
impl Drop for Outlines {
fn drop(&mut self) {
unsafe {
gl::DeleteBuffers(1, &mut self.descriptors_buffer);
gl::DeleteBuffers(1, &mut self.indices_buffer);
gl::DeleteBuffers(1, &mut self.vertices_buffer);
}
}
}
impl Outlines {
#[doc(hidden)]
#[inline]
pub fn vertices_buffer(&self) -> GLuint {
self.vertices_buffer
}
#[doc(hidden)]
#[inline]
pub fn indices_buffer(&self) -> GLuint {
self.indices_buffer
}
#[doc(hidden)]
#[inline]
pub fn descriptors_buffer(&self) -> GLuint {
self.descriptors_buffer
}
#[doc(hidden)]
#[inline]
pub fn descriptor(&self, glyph_index: u16) -> Option<&GlyphDescriptor> {
self.descriptors.get(glyph_index as usize)
}
#[doc(hidden)]
#[inline]
pub fn indices_count(&self) -> usize {
self.indices_count
}
/// Returns the glyph rectangle in font units.
#[inline]
pub fn glyph_bounds(&self, glyph_index: u32) -> GlyphBounds {
self.descriptors[glyph_index as usize].bounds
}
/// Returns the glyph rectangle in fractional pixels.
#[inline]
pub fn glyph_subpixel_bounds(&self, glyph_index: u16, point_size: f32) -> GlyphSubpixelBounds {
self.descriptors[glyph_index as usize].subpixel_bounds(point_size)
}
/// Returns the ID of the glyph with the given index.
#[inline]
pub fn glyph_id(&self, glyph_index: u16) -> u16 {
self.descriptors[glyph_index as usize].glyph_id
}
/// Returns the units per em for the glyph with the given index.
#[inline]
pub fn glyph_units_per_em(&self, glyph_index: u16) -> u32 {
self.descriptors[glyph_index as usize].units_per_em
}
}
#[doc(hidden)]
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct GlyphDescriptor {
bounds: GlyphBounds,
units_per_em: u32,
start_point: u32,
start_index: u32,
glyph_id: u16,
}
impl GlyphDescriptor {
#[doc(hidden)]
#[inline]
pub fn start_index(&self) -> u32 {
self.start_index
}
#[doc(hidden)]
#[inline]
fn subpixel_bounds(&self, point_size: f32) -> GlyphSubpixelBounds {
self.bounds.subpixel_bounds(self.units_per_em as u16, point_size)
}
}
#[doc(hidden)]
#[derive(Copy, Clone, Debug)]
#[repr(C)]
pub struct Vertex {
x: i16,
y: i16,
glyph_index: u16,
}
/// The boundaries of the glyph in fractional pixels.
#[derive(Copy, Clone, Debug)]
pub struct GlyphSubpixelBounds {
pub left: f32,
pub bottom: f32,
pub right: f32,
pub top: f32,
}
impl GlyphSubpixelBounds {
/// Scales the bounds by the given amount.
#[inline]
pub fn scale(&mut self, factor: f32) {
self.left *= factor;
self.bottom *= factor;
self.right *= factor;
self.top *= factor;
}
/// Rounds these bounds out to the nearest pixel.
#[inline]
pub fn round_out(&self) -> GlyphPixelBounds {
GlyphPixelBounds {
left: self.left.floor() as i32,
bottom: self.bottom.floor() as i32,
right: self.right.ceil() as i32,
top: self.top.ceil() as i32,
}
}
/// Returns the total size of the glyph in fractional pixels.
#[inline]
pub fn size(&self) -> Size2D<f32> {
Size2D::new(self.right - self.left, self.top - self.bottom)
}
}
/// The boundaries of the glyph, rounded out to the nearest pixel.
#[derive(Copy, Clone, Debug)]
pub struct GlyphPixelBounds {
pub left: i32,
pub bottom: i32,
pub right: i32,
pub top: i32,
}
impl GlyphPixelBounds {
/// Returns the total size of the glyph in whole pixels.
#[inline]
pub fn size(&self) -> Size2D<i32> {
Size2D::new(self.right - self.left, self.top - self.bottom)
}
}
/// The boundaries of a glyph in font units.
#[derive(Copy, Clone, Default, Debug)]
pub struct GlyphBounds {
pub left: i32,
pub bottom: i32,
pub right: i32,
pub top: i32,
}
impl GlyphBounds {
/// Given the units per em of the font and the point size, returns the fractional boundaries of
/// this glyph.
#[inline]
pub fn subpixel_bounds(&self, units_per_em: u16, point_size: f32) -> GlyphSubpixelBounds {
let pixels_per_unit = point_size / units_per_em as f32;
GlyphSubpixelBounds {
left: self.left as f32 * pixels_per_unit,
bottom: self.bottom as f32 * pixels_per_unit,
right: self.right as f32 * pixels_per_unit,
top: self.top as f32 * pixels_per_unit,
}
}
/// Returns the total size of the glyph in font units.
#[inline]
pub fn size(&self) -> Size2D<i32> {
Size2D::new(self.right - self.left, self.top - self.bottom)
}
}
/// A helper object to construct a single path.
pub struct PathBuilder<'a> {
outline_builder: &'a mut OutlineBuilder,
vbo_start_index: u32,
vbo_end_index: u32,
ibo_start_index: u32,
point_index_in_path: u16,
glyph_index: u16,
}
impl<'a> PathBuilder<'a> {
fn add_point(&mut self, point: &Point2D<i16>) {
self.outline_builder.vertices.push(Vertex {
x: point.x,
y: point.y,
glyph_index: self.glyph_index,
});
self.point_index_in_path += 1;
self.vbo_end_index += 1;
}
/// Moves the pen to the given point.
pub fn move_to(&mut self, point: &Point2D<i16>) {
self.add_point(point)
}
/// Draws a straight line to the given point.
///
/// Panics if a `move_to` has not been issued anywhere prior to this operation.
pub fn line_to(&mut self, point: &Point2D<i16>) {
if self.point_index_in_path == 0 {
panic!("`line_to` must not be the first operation in a path")
}
self.add_point(point);
self.outline_builder.indices.extend_from_slice(&[
self.vbo_end_index - 2,
0,
0,
self.vbo_end_index - 1,
])
}
/// Draws a quadratic Bézier curve to the given point.
///
/// Panics if a `move_to` has not been issued anywhere prior to this operation.
pub fn quad_curve_to(&mut self, p1: &Point2D<i16>, p2: &Point2D<i16>) {
if self.point_index_in_path == 0 {
panic!("`quad_curve_to` must not be the first operation in a path")
}
self.add_point(p1);
self.add_point(p2);
self.outline_builder.indices.extend_from_slice(&[
self.vbo_end_index - 3,
self.vbo_end_index - 2,
self.vbo_end_index - 2,
self.vbo_end_index - 1,
])
}
/// Draws a cubic Bézier curve to the given point.
///
/// Panics if a `move_to` has not been issued anywhere prior to this operation.
pub fn cubic_curve_to(&mut self, p1: &Point2D<i16>, p2: &Point2D<i16>, p3: &Point2D<i16>) {
if self.point_index_in_path == 0 {
panic!("`cubic_curve_to` must not be the first operation in a path")
}
self.add_point(p1);
self.add_point(p2);
self.add_point(p3);
self.outline_builder.indices.extend_from_slice(&[
self.vbo_end_index - 4,
self.vbo_end_index - 3,
self.vbo_end_index - 2,
self.vbo_end_index - 1
])
}
/// Finishes the path.
pub fn finish(self, bounds: &GlyphBounds, units_per_em: u32, glyph_id: u16) {
self.outline_builder.descriptors.push(GlyphDescriptor {
bounds: *bounds,
units_per_em: units_per_em,
start_point: self.vbo_start_index,
start_index: self.ibo_start_index,
glyph_id: glyph_id,
})
}
}

View File

@ -1,543 +0,0 @@
// Copyright 2017 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! A GPU rasterizer for glyphs.
use atlas::Atlas;
use compute_shader::device::Device;
use compute_shader::image::{Format, Image};
use compute_shader::instance::{Instance, ShadingLanguage};
use compute_shader::profile_event::ProfileEvent;
use compute_shader::program::Program;
use compute_shader::queue::{Queue, Uniform};
use coverage::CoverageBuffer;
use error::{InitError, RasterError};
use euclid::rect::Rect;
use gl::types::{GLchar, GLenum, GLint, GLsizei, GLuint, GLvoid};
use gl;
use outline::{Outlines, Vertex};
use std::ascii::AsciiExt;
use std::env;
use std::fs::File;
use std::io::Read;
use std::mem;
use std::path::{Path, PathBuf};
use std::ptr;
static COMPUTE_PREAMBLE_FILENAME: &'static str = "preamble.cs.glsl";
static ACCUM_COMMON_CL_SHADER_FILENAME: &'static str = "accum_common.cl";
static ACCUM_COMMON_COMPUTE_SHADER_FILENAME: &'static str = "accum_common.cs.glsl";
static ACCUM_GRAY_CL_SHADER_FILENAME: &'static str = "accum_gray.cl";
static ACCUM_SUBPIXEL_CL_SHADER_FILENAME: &'static str = "accum_subpixel.cl";
static ACCUM_GRAY_COMPUTE_SHADER_FILENAME: &'static str = "accum.cs.glsl";
static ACCUM_SUBPIXEL_COMPUTE_SHADER_FILENAME: &'static str = "accum.cs.glsl";
static DRAW_VERTEX_SHADER_FILENAME: &'static str = "draw.vs.glsl";
static DRAW_TESS_CONTROL_SHADER_FILENAME: &'static str = "draw.tcs.glsl";
static DRAW_TESS_EVALUATION_SHADER_FILENAME: &'static str = "draw.tes.glsl";
static DRAW_GEOMETRY_SHADER_FILENAME: &'static str = "draw.gs.glsl";
static DRAW_FRAGMENT_SHADER_FILENAME: &'static str = "draw.fs.glsl";
/// A GPU rasterizer for glyphs.
pub struct Rasterizer {
device: Device,
queue: Queue,
shading_language: ShadingLanguage,
draw_program: GLuint,
accum_program_gray_r8: Program,
accum_program_gray_rgba8: Program,
accum_program_subpixel_rgba8: Program,
draw_vertex_array: GLuint,
draw_position_attribute: GLint,
draw_glyph_index_attribute: GLint,
draw_atlas_size_uniform: GLint,
draw_subpixel_aa_uniform: GLint,
draw_glyph_descriptors_uniform: GLuint,
draw_image_descriptors_uniform: GLuint,
draw_query: GLuint,
options: RasterizerOptions,
}
/// Profiling events that can be used to profile Pathfinder's performance.
pub struct DrawAtlasProfilingEvents {
/// An OpenGL timer query object that measures the length of time that Pathfinder took to draw
/// the glyph edges.
///
/// You can get the results with `gl::GetQueryObjectui64v(..., gl::TIME_ELAPSED, ...)`.
pub draw: GLuint,
/// A `compute-shader` profile event that measures the length of time that Pathfinder took to
/// perform the accumulation (fill) step.
pub accum: ProfileEvent,
}
impl Rasterizer {
/// Creates a new rasterizer.
///
/// This rasterizer can be used for as many draw calls as you like.
///
/// * `instance` is the `compute-shader` instance to use.
///
/// * `device` is the compute device to use.
///
/// * `queue` is the queue on that compute device to use.
///
/// * `options` is a set of options that control the rasterizer's behavior.
pub fn new(instance: &Instance, device: Device, queue: Queue, options: RasterizerOptions)
-> Result<Rasterizer, InitError> {
let (draw_program, draw_position_attribute, draw_glyph_index_attribute);
let (draw_glyph_descriptors_uniform, draw_image_descriptors_uniform);
let (draw_atlas_size_uniform, draw_subpixel_aa_uniform);
let (mut draw_vertex_array, mut draw_query) = (0, 0);
unsafe {
draw_program = gl::CreateProgram();
let vertex_shader = try!(compile_gl_shader(gl::VERTEX_SHADER,
"Vertex shader",
DRAW_VERTEX_SHADER_FILENAME,
&options.shader_path));
gl::AttachShader(draw_program, vertex_shader);
let fragment_shader = try!(compile_gl_shader(gl::FRAGMENT_SHADER,
"Fragment shader",
DRAW_FRAGMENT_SHADER_FILENAME,
&options.shader_path));
gl::AttachShader(draw_program, fragment_shader);
if options.force_geometry_shader {
let geometry_shader = try!(compile_gl_shader(gl::GEOMETRY_SHADER,
"Geometry shader",
DRAW_GEOMETRY_SHADER_FILENAME,
&options.shader_path));
gl::AttachShader(draw_program, geometry_shader);
} else {
let tess_control_shader = try!(compile_gl_shader(gl::TESS_CONTROL_SHADER,
"Tessellation control shader",
DRAW_TESS_CONTROL_SHADER_FILENAME,
&options.shader_path));
gl::AttachShader(draw_program, tess_control_shader);
let tess_evaluation_shader =
try!(compile_gl_shader(gl::TESS_EVALUATION_SHADER,
"Tessellation evaluation shader",
DRAW_TESS_EVALUATION_SHADER_FILENAME,
&options.shader_path));
gl::AttachShader(draw_program, tess_evaluation_shader);
}
gl::LinkProgram(draw_program);
try!(check_gl_object_status(draw_program,
gl::LINK_STATUS,
gl::GetProgramiv,
gl::GetProgramInfoLog).map_err(InitError::LinkFailed));
gl::GenVertexArrays(1, &mut draw_vertex_array);
draw_position_attribute =
gl::GetAttribLocation(draw_program, b"aPosition\0".as_ptr() as *const GLchar);
draw_glyph_index_attribute =
gl::GetAttribLocation(draw_program, b"aGlyphIndex\0".as_ptr() as *const GLchar);
draw_atlas_size_uniform =
gl::GetUniformLocation(draw_program, b"uAtlasSize\0".as_ptr() as *const GLchar);
draw_subpixel_aa_uniform =
gl::GetUniformLocation(draw_program, b"uSubpixelAA\0".as_ptr() as *const GLchar);
draw_glyph_descriptors_uniform =
gl::GetUniformBlockIndex(draw_program,
b"ubGlyphDescriptors\0".as_ptr() as *const GLchar);
draw_image_descriptors_uniform =
gl::GetUniformBlockIndex(draw_program,
b"ubImageDescriptors\0".as_ptr() as *const GLchar);
gl::GenQueries(1, &mut draw_query)
}
// FIXME(pcwalton): Don't panic if this fails to compile; just return an error.
let (accum_filename_common, accum_filename_gray, accum_filename_subpixel);
let shading_language = instance.shading_language();
match shading_language {
ShadingLanguage::Cl => {
accum_filename_common = ACCUM_COMMON_CL_SHADER_FILENAME;
accum_filename_gray = ACCUM_GRAY_CL_SHADER_FILENAME;
accum_filename_subpixel = ACCUM_SUBPIXEL_CL_SHADER_FILENAME;
}
ShadingLanguage::Glsl => {
accum_filename_common = ACCUM_COMMON_COMPUTE_SHADER_FILENAME;
accum_filename_gray = ACCUM_GRAY_COMPUTE_SHADER_FILENAME;
accum_filename_subpixel = ACCUM_SUBPIXEL_COMPUTE_SHADER_FILENAME;
}
}
let mut accum_path_common = options.shader_path.to_owned();
let mut accum_path_gray = accum_path_common.clone();
let mut accum_path_subpixel = accum_path_common.clone();
accum_path_common.push(accum_filename_common);
accum_path_gray.push(accum_filename_gray);
accum_path_subpixel.push(accum_filename_subpixel);
let mut accum_file_common = match File::open(&accum_path_common) {
Err(error) => return Err(InitError::ShaderUnreadable(error)),
Ok(file) => file,
};
let mut accum_file_gray = match File::open(&accum_path_gray) {
Err(error) => return Err(InitError::ShaderUnreadable(error)),
Ok(file) => file,
};
let mut accum_file_subpixel = match File::open(&accum_path_subpixel) {
Err(error) => return Err(InitError::ShaderUnreadable(error)),
Ok(file) => file,
};
let mut compute_preamble_source = String::new();
match shading_language {
ShadingLanguage::Cl => {}
ShadingLanguage::Glsl => {
let mut compute_preamble_path = options.shader_path.to_owned();
compute_preamble_path.push(COMPUTE_PREAMBLE_FILENAME);
let mut compute_preamble_file = match File::open(&compute_preamble_path) {
Err(error) => return Err(InitError::ShaderUnreadable(error)),
Ok(file) => file,
};
if compute_preamble_file.read_to_string(&mut compute_preamble_source).is_err() {
return Err(InitError::CompileFailed("Compute shader",
"Invalid UTF-8".to_string()))
}
}
}
let mut accum_source_common = String::new();
let mut accum_source_gray = String::new();
let mut accum_source_subpixel = String::new();
if accum_file_common.read_to_string(&mut accum_source_common).is_err() {
return Err(InitError::CompileFailed("Compute shader", "Invalid UTF-8".to_string()))
}
if accum_file_gray.read_to_string(&mut accum_source_gray).is_err() {
return Err(InitError::CompileFailed("Compute shader", "Invalid UTF-8".to_string()))
}
if accum_file_subpixel.read_to_string(&mut accum_source_subpixel).is_err() {
return Err(InitError::CompileFailed("Compute shader", "Invalid UTF-8".to_string()))
}
let accum_source_gray_r8 = format!("{}\n#define IMAGE_FORMAT r8\n{}\n{}",
compute_preamble_source,
accum_source_common,
accum_source_gray);
let accum_source_gray_rgba8 = format!("{}\n#define IMAGE_FORMAT rgba8\n{}\n{}",
compute_preamble_source,
accum_source_common,
accum_source_gray);
let accum_source_subpixel_rgba8 = format!("{}\n#define IMAGE_FORMAT rgba8\n{}\n{}",
compute_preamble_source,
accum_source_common,
accum_source_subpixel);
let accum_program_gray_r8 = try!(device.create_program(&accum_source_gray_r8)
.map_err(InitError::ComputeError));
let accum_program_gray_rgba8 = try!(device.create_program(&accum_source_gray_rgba8)
.map_err(InitError::ComputeError));
let accum_program_subpixel_rgba8 = try!(device.create_program(&accum_source_subpixel_rgba8)
.map_err(InitError::ComputeError));
Ok(Rasterizer {
device: device,
queue: queue,
shading_language: shading_language,
draw_program: draw_program,
accum_program_gray_r8: accum_program_gray_r8,
accum_program_gray_rgba8: accum_program_gray_rgba8,
accum_program_subpixel_rgba8: accum_program_subpixel_rgba8,
draw_vertex_array: draw_vertex_array,
draw_position_attribute: draw_position_attribute,
draw_glyph_index_attribute: draw_glyph_index_attribute,
draw_atlas_size_uniform: draw_atlas_size_uniform,
draw_subpixel_aa_uniform: draw_subpixel_aa_uniform,
draw_glyph_descriptors_uniform: draw_glyph_descriptors_uniform,
draw_image_descriptors_uniform: draw_image_descriptors_uniform,
draw_query: draw_query,
options: options,
})
}
/// Draws the supplied font atlas into the texture image at the given location.
///
/// * `image` is the texture image that this rasterizer will draw into.
///
/// * `rect` is the pixel boundaries of the atlas inside that image.
///
/// * `atlas` is the glyph atlas to render.
///
/// * `outlines` specifies the outlines for the font associated with that atlas.
///
/// * `coverage_buffer` is a coverage buffer to use (see `CoverageBuffer`). This can be reused
/// from call to call. It must be at least as large as the atlas.
///
/// Note that, if the atlas is empty, `RasterError::NoGlyphsToDraw` is returned.
pub fn draw_atlas(&self,
image: &Image,
rect: &Rect<u32>,
atlas: &Atlas,
outlines: &Outlines,
coverage_buffer: &CoverageBuffer)
-> Result<DrawAtlasProfilingEvents, RasterError> {
if atlas.is_empty() {
return Err(RasterError::NoGlyphsToDraw)
}
unsafe {
gl::BindFramebuffer(gl::FRAMEBUFFER, coverage_buffer.framebuffer());
// Save the old viewport so we can restore it later.
let mut old_viewport: [GLint; 4] = [0; 4];
gl::GetIntegerv(gl::VIEWPORT, old_viewport.as_mut_ptr());
// Set up our new viewport.
let viewport_width = if atlas.uses_subpixel_antialiasing() {
rect.size.width * 3
} else {
rect.size.width
};
gl::Viewport(0, 0, viewport_width as GLint, rect.size.height as GLint);
// TODO(pcwalton): Scissor to the image rect to clear faster?
gl::ClearColor(0.0, 0.0, 0.0, 1.0);
gl::Clear(gl::COLOR_BUFFER_BIT);
gl::BindVertexArray(self.draw_vertex_array);
gl::UseProgram(self.draw_program);
// Set up the buffer layout.
gl::BindBuffer(gl::ARRAY_BUFFER, outlines.vertices_buffer());
gl::VertexAttribIPointer(self.draw_position_attribute as GLuint,
2,
gl::SHORT,
mem::size_of::<Vertex>() as GLint,
0 as *const GLvoid);
gl::VertexAttribIPointer(self.draw_glyph_index_attribute as GLuint,
1,
gl::UNSIGNED_SHORT,
mem::size_of::<Vertex>() as GLint,
mem::size_of::<(i16, i16)>() as *const GLvoid);
gl::EnableVertexAttribArray(self.draw_position_attribute as GLuint);
gl::EnableVertexAttribArray(self.draw_glyph_index_attribute as GLuint);
gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, outlines.indices_buffer());
// Don't bind the atlas uniform buffers (binding point 2) here; the batches will do
// that on their own.
gl::BindBufferBase(gl::UNIFORM_BUFFER, 1, outlines.descriptors_buffer());
gl::UniformBlockBinding(self.draw_program, self.draw_glyph_descriptors_uniform, 1);
gl::UniformBlockBinding(self.draw_program, self.draw_image_descriptors_uniform, 2);
gl::Uniform2ui(self.draw_atlas_size_uniform, viewport_width, rect.size.height);
gl::Uniform1i(self.draw_subpixel_aa_uniform,
atlas.uses_subpixel_antialiasing() as GLint);
gl::PatchParameteri(gl::PATCH_VERTICES, 4);
// Use blending on our floating point framebuffer to accumulate coverage.
gl::Enable(gl::BLEND);
gl::BlendEquation(gl::FUNC_ADD);
gl::BlendFunc(gl::ONE, gl::ONE);
// Enable backface culling. See comments in `draw.tcs.glsl` for more information
// regarding why this is necessary.
gl::CullFace(gl::BACK);
gl::FrontFace(gl::CCW);
gl::Enable(gl::CULL_FACE);
// If we're using a geometry shader for debugging, we draw fake triangles. Otherwise,
// we use patches.
let primitive = if self.options.force_geometry_shader {
gl::TRIANGLES
} else {
gl::PATCHES
};
// Now draw the glyph ranges.
gl::BeginQuery(gl::TIME_ELAPSED, self.draw_query);
atlas.draw(primitive);
gl::EndQuery(gl::TIME_ELAPSED);
gl::Disable(gl::CULL_FACE);
gl::Disable(gl::BLEND);
// Restore our old framebuffer and viewport.
gl::BindFramebuffer(gl::FRAMEBUFFER, 0);
gl::Viewport(old_viewport[0], old_viewport[1], old_viewport[2], old_viewport[3]);
// FIXME(pcwalton): We should have some better synchronization here if we're using
// OpenCL, but I don't know how to do that portably (i.e. on Mac…) Just using
// `glFlush()` seems to work in practice.
gl::Flush();
if self.shading_language == ShadingLanguage::Glsl {
gl::MemoryBarrier(gl::ALL_BARRIER_BITS);
}
}
let accum_uniforms = [
(0, Uniform::Image(image)),
(1, Uniform::Image(coverage_buffer.image())),
(2, Uniform::UVec4([rect.origin.x, rect.origin.y, rect.max_x(), rect.max_y()])),
(3, Uniform::U32(atlas.shelf_height())),
];
let accum_program = match (image.format(), atlas.uses_subpixel_antialiasing()) {
(Ok(Format::R8), false) => &self.accum_program_gray_r8,
(Ok(Format::RGBA8), false) => &self.accum_program_gray_rgba8,
(Ok(Format::RGBA8), true) => &self.accum_program_subpixel_rgba8,
(Ok(_), _) => return Err(RasterError::UnsupportedImageFormat),
(Err(err), _) => return Err(RasterError::ComputeError(err)),
};
let accum_event = try!(self.queue.submit_compute(accum_program,
&[atlas.shelf_columns()],
&accum_uniforms,
&[]).map_err(RasterError::ComputeError));
Ok(DrawAtlasProfilingEvents {
draw: self.draw_query,
accum: accum_event,
})
}
/// Returns the GPU compute device that this rasterizer is using.
#[inline]
pub fn device(&self) -> &Device {
&self.device
}
/// Returns the GPU compute queue that this rasterizer is using.
#[inline]
pub fn queue(&self) -> &Queue {
&self.queue
}
}
fn compile_gl_shader(shader_type: GLuint,
description: &'static str,
filename: &str,
shader_path: &Path)
-> Result<GLuint, InitError> {
unsafe {
let mut path = shader_path.to_owned();
path.push(filename);
let mut file = match File::open(&path) {
Err(error) => return Err(InitError::ShaderUnreadable(error)),
Ok(file) => file,
};
let mut source = String::new();
if file.read_to_string(&mut source).is_err() {
return Err(InitError::CompileFailed(description, "Invalid UTF-8".to_string()))
}
let shader = gl::CreateShader(shader_type);
gl::ShaderSource(shader, 1, &(source.as_ptr() as *const GLchar), &(source.len() as GLint));
gl::CompileShader(shader);
match check_gl_object_status(shader,
gl::COMPILE_STATUS,
gl::GetShaderiv,
gl::GetShaderInfoLog) {
Ok(_) => Ok(shader),
Err(info_log) => Err(InitError::CompileFailed(description, info_log)),
}
}
}
fn check_gl_object_status(object: GLuint,
parameter: GLenum,
get_status: unsafe fn(GLuint, GLenum, *mut GLint),
get_log: unsafe fn(GLuint, GLsizei, *mut GLsizei, *mut GLchar))
-> Result<(), String> {
unsafe {
let mut status = 0;
get_status(object, parameter, &mut status);
if status == gl::TRUE as i32 {
return Ok(())
}
let mut info_log_length = 0;
get_status(object, gl::INFO_LOG_LENGTH, &mut info_log_length);
let mut info_log = vec![0; info_log_length as usize];
get_log(object, info_log_length, ptr::null_mut(), info_log.as_mut_ptr() as *mut GLchar);
match String::from_utf8(info_log) {
Ok(string) => Err(string),
Err(_) => Err("(not UTF-8)".to_owned()),
}
}
}
/// Options that control Pathfinder's behavior.
#[derive(Clone, Debug)]
pub struct RasterizerOptions {
/// The path to the shaders.
///
/// If not specified, then the current directory is used. This is probably not what you want.
/// The corresponding environment variable is `PATHFINDER_SHADER_PATH`.
pub shader_path: PathBuf,
/// If true, then a geometry shader is used instead of a tessellation shader.
///
/// This will probably negatively impact performance. This should be considered a debugging
/// feature only.
///
/// The default is false. The corresponding environment variable is
/// `PATHFINDER_FORCE_GEOMETRY_SHADER`.
pub force_geometry_shader: bool,
}
impl Default for RasterizerOptions {
fn default() -> RasterizerOptions {
RasterizerOptions {
shader_path: PathBuf::from("."),
force_geometry_shader: false,
}
}
}
impl RasterizerOptions {
/// Takes rasterization options from environment variables.
///
/// See the fields of `RasterizerOptions` for info on the settings, including the environment
/// variables that control them.
///
/// Boolean variables may be set to true by setting the corresponding variable to `"on"`,
/// `"yes"`, or `1`; they may be set to false with `"off"`, `"no"`, or `0`.
///
/// Environment variables not set cause their associated settings to take on default values.
pub fn from_env() -> Result<RasterizerOptions, InitError> {
let shader_path = match env::var("PATHFINDER_SHADER_PATH") {
Ok(ref string) => PathBuf::from(string),
Err(_) => PathBuf::from("."),
};
let force_geometry_shader = match env::var("PATHFINDER_FORCE_GEOMETRY_SHADER") {
Ok(ref string) if string.eq_ignore_ascii_case("on") ||
string.eq_ignore_ascii_case("yes") ||
string.eq_ignore_ascii_case("1") => true,
Ok(ref string) if string.eq_ignore_ascii_case("off") ||
string.eq_ignore_ascii_case("no") ||
string.eq_ignore_ascii_case("0") => false,
Err(_) => false,
Ok(_) => return Err(InitError::InvalidSetting),
};
Ok(RasterizerOptions {
shader_path: shader_path,
force_geometry_shader: force_geometry_shader,
})
}
}

View File

@ -1,119 +0,0 @@
// Copyright 2017 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use euclid::{Point2D, Rect, Size2D};
pub struct RectPacker {
free_rects: Vec<Rect<u32>>,
available_width: u32,
shelf_height: u32,
shelf_count: u32,
/// The amount of horizontal space allocated in the last shelf.
width_of_last_shelf: u32,
}
impl RectPacker {
#[inline]
pub fn new(available_width: u32, shelf_height: u32) -> RectPacker {
RectPacker {
free_rects: vec![],
available_width: available_width,
shelf_height: shelf_height,
shelf_count: 0,
width_of_last_shelf: 0,
}
}
/// Packs a rectangle of the given size.
///
/// Returns the top-left position of the rectangle or an error if there is no space left.
pub fn pack(&mut self, size: &Size2D<u32>) -> Result<Point2D<u32>, ()> {
// Add a one-pixel border to prevent bleed.
let alloc_size = *size + Size2D::new(2, 2);
// If the allocation size is less than our shelf height, we will always fail.
if alloc_size.height > self.shelf_height {
return Err(())
}
let chosen_index_and_rect =
self.free_rects
.iter()
.enumerate()
.filter(|&(_, rect)| {
alloc_size.width <= rect.size.width && alloc_size.height <= rect.size.height
})
.min_by(|&(_, a), &(_, b)| area(a).cmp(&area(b)))
.map(|(index, rect)| (index, *rect));
let chosen_rect;
match chosen_index_and_rect {
None => {
// Make a new shelf.
chosen_rect = Rect::new(Point2D::new(0, self.shelf_height * self.shelf_count),
Size2D::new(self.available_width, self.shelf_height));
self.shelf_count += 1;
self.width_of_last_shelf = 0
}
Some((index, rect)) => {
self.free_rects.swap_remove(index);
chosen_rect = rect;
}
}
// Guillotine to bottom.
let free_below =
Rect::new(Point2D::new(chosen_rect.origin.x, chosen_rect.origin.y + alloc_size.height),
Size2D::new(alloc_size.width, chosen_rect.size.height - alloc_size.height));
if !free_below.is_empty() {
self.free_rects.push(free_below);
}
// Guillotine to right.
let free_to_right =
Rect::new(Point2D::new(chosen_rect.origin.x + alloc_size.width, chosen_rect.origin.y),
Size2D::new(chosen_rect.size.width - alloc_size.width,
chosen_rect.size.height));
if !free_to_right.is_empty() {
self.free_rects.push(free_to_right);
}
// Update width of last shelf if necessary.
let on_last_shelf = chosen_rect.max_y() >= self.shelf_height * (self.shelf_count - 1);
if on_last_shelf && self.width_of_last_shelf < chosen_rect.max_x() {
self.width_of_last_shelf = chosen_rect.max_x()
}
let object_origin = chosen_rect.origin + Point2D::new(1, 1);
Ok(object_origin)
}
#[inline]
pub fn shelf_height(&self) -> u32 {
self.shelf_height
}
#[inline]
pub fn shelf_columns(&self) -> u32 {
let full_shelf_count = if self.shelf_count == 0 {
0
} else {
self.shelf_count - 1
};
full_shelf_count * self.available_width + self.width_of_last_shelf
}
}
#[inline]
fn area(rect: &Rect<u32>) -> u32 {
rect.size.width * rect.size.height
}

View File

@ -1,65 +0,0 @@
// Copyright 2017 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! A very basic text shaper for simple needs.
//!
//! Do not use this for international or high-quality text. This shaper does not do kerning,
//! ligation, or advanced typography features (`GSUB`, `GPOS`, text morphing). Consider HarfBuzz or
//! the system shaper instead.
use charmap::GlyphMapping;
use font::Font;
/// Shapes the given Unicode text in the given font, returning the proper position for each glyph.
///
/// See the description of this module for caveats.
///
/// For proper operation, the given `glyph_mapping` must include all the glyphs necessary to render
/// the string.
pub fn shape_text(font: &Font, glyph_mapping: &GlyphMapping, string: &str) -> Vec<GlyphPos> {
let mut chars = string.chars().peekable();
let mut next_glyph_id = None;
let mut result = vec![];
while let Some(ch) = chars.next() {
let glyph_id = match next_glyph_id.take() {
None => glyph_mapping.glyph_for(ch as u32).unwrap_or(0),
Some(next_glyph_id) => next_glyph_id,
};
let mut advance = match font.metrics_for_glyph(glyph_id) {
Err(_) => 0,
Ok(metrics) => metrics.advance_width as i16,
};
if let Some(&next_char) = chars.peek() {
let next_glyph = glyph_mapping.glyph_for(next_char as u32).unwrap_or(0);
next_glyph_id = Some(next_glyph);
advance += font.kerning_for_glyph_pair(glyph_id, next_glyph)
}
result.push(GlyphPos {
glyph_id: glyph_id,
advance: advance,
})
}
result
}
/// The position of a glyph after shaping.
#[derive(Clone, Copy, Debug)]
pub struct GlyphPos {
/// The glyph ID to emit.
pub glyph_id: u16,
/// The amount to move the cursor forward *after* emitting this glyph.
pub advance: i16,
}

View File

@ -1,597 +0,0 @@
// Copyright 2017 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use byteorder::{BigEndian, ReadBytesExt};
use error::FontError;
use euclid::Point2D;
use font::{FontTable, Point, PointKind};
use outline::GlyphBounds;
use std::cmp;
use std::u16;
use util::Jump;
pub const TAG: u32 = ((b'C' as u32) << 24) |
((b'F' as u32) << 16) |
((b'F' as u32) << 8) |
(b' ' as u32);
#[derive(Clone, Copy, Debug)]
pub struct CffTable<'a> {
// The offset of the char strings INDEX.
char_strings: u32,
table: FontTable<'a>,
}
impl<'a> CffTable<'a> {
#[inline]
pub fn new(table: FontTable) -> Result<CffTable, FontError> {
let mut reader = table.bytes;
// Check version.
let major = try!(reader.read_u8().map_err(FontError::eof));
let minor = try!(reader.read_u8().map_err(FontError::eof));
if major != 1 || minor != 0 {
return Err(FontError::UnsupportedCffVersion)
}
// Skip the header.
let hdr_size = try!(reader.read_u8().map_err(FontError::eof));
try!(reader.jump(hdr_size as usize - 3).map_err(FontError::eof));
// Skip the name INDEX.
//
// TODO(pcwalton): What to do if there are multiple fonts here?
try!(skip_index(&mut reader));
// Get the top DICT for our font.
if try!(find_in_index(&mut reader, 0)).is_none() {
return Err(FontError::CffTopDictNotFound)
}
// Find the CharStrings offset within the top DICT.
let char_strings = try!(get_integer_in_dict(&mut reader, 17));
// Skip the string INDEX.
try!(skip_index(&mut reader));
// Ignore the global subr INDEX for now.
//
// TODO(pcwalton): Support global subroutines.
Ok(CffTable {
char_strings: char_strings as u32,
table: table,
})
}
pub fn for_each_point<F>(&self, glyph_id: u16, mut callback: F)
-> Result<(), FontError> where F: FnMut(&Point) {
let mut reader = self.table.bytes;
try!(reader.jump(self.char_strings as usize).map_err(FontError::eof));
let char_string_length = match try!(find_in_index(&mut reader, glyph_id)) {
Some(char_string_length) => char_string_length,
None => return Err(FontError::UnexpectedEof),
};
let mut reader = &reader[0..char_string_length as usize];
let mut stack = EvaluationStack::new();
let (mut start, mut pos) = (Point2D::new(0, 0), Point2D::new(0, 0));
let mut index_in_contour = 0;
let mut hint_count = 0;
// FIXME(pcwalton): This shouldn't panic on stack bounds check failures.
while let Ok(b0) = reader.read_u8() {
match b0 {
32...246 => try!(stack.push(b0 as i32 - 139)),
247...250 => {
let b1 = try!(reader.read_u8().map_err(FontError::eof));
try!(stack.push((b0 as i32 - 247) * 256 + b1 as i32 + 108))
}
251...254 => {
let b1 = try!(reader.read_u8().map_err(FontError::eof));
try!(stack.push((b0 as i32 - 251) * -256 - b1 as i32 - 108))
}
255 => {
// FIXME(pcwalton): Don't truncate the lower 16 bits.
try!(stack.push(try!(reader.read_i32::<BigEndian>().map_err(FontError::eof)) >>
16))
}
28 => {
let number = try!(reader.read_i16::<BigEndian>().map_err(FontError::eof)) as i32;
try!(stack.push(number))
}
4 => {
// |- dy1 vmoveto
close_path_if_necessary(&start, index_in_contour, &mut callback);
pos.y += stack.array[0] as i16;
callback(&Point {
position: pos,
index_in_contour: 0,
kind: PointKind::OnCurve,
});
start = pos;
index_in_contour = 1;
stack.clear()
}
5 => {
// |- {dxa dya}+ rlineto
for points in stack.array[0..stack.size as usize].chunks(2) {
pos = pos + Point2D::new(points[0] as i16, points[1] as i16);
callback(&Point {
position: pos,
index_in_contour: index_in_contour,
kind: PointKind::OnCurve,
});
index_in_contour += 1
}
stack.clear()
}
6 => {
// |- dx1 {dya dxb}* hlineto
// |- {dxa dyb}* hlineto
for (i, length) in stack.array[0..stack.size as usize].iter().enumerate() {
if i % 2 == 0 {
pos.x += *length as i16
} else {
pos.y += *length as i16
}
callback(&Point {
position: pos,
index_in_contour: index_in_contour,
kind: PointKind::OnCurve,
});
index_in_contour += 1
}
stack.clear()
}
7 => {
// |- dy1 {dxa dyb}* vlineto
// |- {dya dxb}* vlineto
for (i, length) in stack.array[0..stack.size as usize].iter().enumerate() {
if i % 2 == 0 {
pos.y += *length as i16
} else {
pos.x += *length as i16
}
callback(&Point {
position: pos,
index_in_contour: index_in_contour,
kind: PointKind::OnCurve,
});
index_in_contour += 1
}
stack.clear()
}
8 => {
// |- {dxa dya dxb dyb dxc dyc}+ rrcurveto (8)
for chunk in stack.array[0..stack.size as usize].chunks(6) {
add_curve(chunk[0] as i16, chunk[1] as i16,
chunk[2] as i16, chunk[3] as i16,
chunk[4] as i16, chunk[5] as i16,
&mut pos,
&mut index_in_contour,
&mut callback)
}
stack.clear()
}
24 => {
// |- {dxa dya dxb dyb dxc dyc}+ dxd dyd rcurveline (24)
for chunk in stack.array[0..stack.size as usize - 2].chunks(6) {
add_curve(chunk[0] as i16, chunk[1] as i16,
chunk[2] as i16, chunk[3] as i16,
chunk[4] as i16, chunk[5] as i16,
&mut pos,
&mut index_in_contour,
&mut callback)
}
pos = pos + Point2D::new(stack.array[stack.size as usize - 2] as i16,
stack.array[stack.size as usize - 1] as i16);
callback(&Point {
position: pos,
index_in_contour: index_in_contour,
kind: PointKind::OnCurve,
});
index_in_contour += 1;
stack.clear()
}
25 => {
// |- {dxa dya}+ dxb dyb dxc dyc dxd dyd rlinecurve (25)
for chunk in stack.array[0..stack.size as usize - 6].chunks(2) {
pos = pos + Point2D::new(chunk[0] as i16, chunk[1] as i16);
callback(&Point {
position: pos,
index_in_contour: index_in_contour,
kind: PointKind::OnCurve,
});
index_in_contour += 1;
}
add_curve(stack.array[stack.size as usize - 6] as i16,
stack.array[stack.size as usize - 5] as i16,
stack.array[stack.size as usize - 4] as i16,
stack.array[stack.size as usize - 3] as i16,
stack.array[stack.size as usize - 2] as i16,
stack.array[stack.size as usize - 1] as i16,
&mut pos,
&mut index_in_contour,
&mut callback);
stack.clear()
}
30 => {
// |- dy1 dx2 dy2 dx3 {dxa dxb dyb dyc dyd dxe dye dxf}* dyf? vhcurveto (30)
// |- {dya dxb dyb dxc dxd dxe dye dyf}+ dxf? vhcurveto (30)
for (i, chunk) in stack.array[0..stack.size as usize].chunks(4).enumerate() {
if chunk.len() != 4 {
break
}
let dxyf = if i * 4 + 5 == stack.size as usize {
stack.array[stack.size as usize - 1]
} else {
0
};
if i % 2 == 0 {
add_curve(0, chunk[0] as i16,
chunk[1] as i16, chunk[2] as i16,
chunk[3] as i16, dxyf as i16,
&mut pos,
&mut index_in_contour,
&mut callback)
} else {
add_curve(chunk[0] as i16, 0,
chunk[1] as i16, chunk[2] as i16,
dxyf as i16, chunk[3] as i16,
&mut pos,
&mut index_in_contour,
&mut callback)
}
}
stack.clear()
}
31 => {
// |- dx1 dx2 dy2 dy3 {dya dxb dyb dxc dxd dxe dye dyf}* dxf? hvcurveto (31)
// |- {dxa dxb dyb dyc dyd dxe dye dxf}+ dyf? hvcurveto (31)
for (i, chunk) in stack.array[0..stack.size as usize].chunks(4).enumerate() {
if chunk.len() != 4 {
break
}
let dxyf = if i * 4 + 5 == stack.size as usize {
stack.array[stack.size as usize - 1]
} else {
0
};
if i % 2 == 0 {
add_curve(chunk[0] as i16, 0,
chunk[1] as i16, chunk[2] as i16,
dxyf as i16, chunk[3] as i16,
&mut pos,
&mut index_in_contour,
&mut callback)
} else {
add_curve(0, chunk[0] as i16,
chunk[1] as i16, chunk[2] as i16,
chunk[3] as i16, dxyf as i16,
&mut pos,
&mut index_in_contour,
&mut callback)
}
}
stack.clear()
}
26 => {
// |- dx1? {dya dxb dyb dyc}+ vvcurveto (26)
let start;
if stack.size % 2 == 0 {
start = 0
} else {
pos.x += stack.array[0] as i16;
start = 1
}
for chunk in stack.array[start..stack.size as usize].chunks(4) {
add_curve(0, chunk[0] as i16,
chunk[1] as i16, chunk[2] as i16,
0, chunk[3] as i16,
&mut pos,
&mut index_in_contour,
&mut callback)
}
stack.clear()
}
27 => {
// |- dy1? {dxa dxb dyb dxc}+ hhcurveto (27)
let start;
if stack.size % 2 == 0 {
start = 0
} else {
pos.y += stack.array[0] as i16;
start = 1
}
for chunk in stack.array[start..stack.size as usize].chunks(4) {
add_curve(chunk[0] as i16, 0,
chunk[1] as i16, chunk[2] as i16,
chunk[3] as i16, 0,
&mut pos,
&mut index_in_contour,
&mut callback)
}
stack.clear()
}
14 => {
// endchar
break
}
1 | 18 => {
// hstem hint (ignored)
hint_count += stack.size as u16 / 2;
stack.clear()
}
3 | 23 => {
// vstem hint (ignored)
hint_count += stack.size as u16 / 2;
stack.clear()
}
19 => {
// hintmask (ignored)
//
// First, process an implicit vstem hint.
//
// FIXME(pcwalton): Should only do that if we're in the header.
hint_count += stack.size as u16 / 2;
stack.clear();
// Now skip ⌈hint_count / 8⌉ bytes.
let hint_byte_count = (hint_count as usize + 7) / 8;
try!(reader.jump(hint_byte_count).map_err(FontError::eof));
}
20 => {
// Skip ⌈hint_count / 8⌉ bytes.
stack.clear();
let hint_byte_count = (hint_count as usize + 7) / 8;
try!(reader.jump(hint_byte_count).map_err(FontError::eof));
}
21 => {
// |- dx1 dy1 rmoveto
close_path_if_necessary(&start, index_in_contour, &mut callback);
pos = pos + Point2D::new(stack.array[0] as i16, stack.array[1] as i16);
callback(&Point {
position: pos,
index_in_contour: 0,
kind: PointKind::OnCurve,
});
start = pos;
index_in_contour = 1;
stack.clear()
}
22 => {
// |- dx1 hmoveto
close_path_if_necessary(&start, index_in_contour, &mut callback);
pos.x += stack.array[0] as i16;
callback(&Point {
position: pos,
index_in_contour: 0,
kind: PointKind::OnCurve,
});
start = pos;
index_in_contour = 1;
stack.clear()
}
12 => {
// TODO(pcwalton): Support these extended operators.
let _operator = (12 << 8) |
(try!(reader.read_u8().map_err(FontError::eof)) as u32);
stack.clear();
return Err(FontError::CffUnimplementedOperator)
}
_ => {
stack.clear();
return Err(FontError::CffUnimplementedOperator)
}
}
}
close_path_if_necessary(&start, index_in_contour, &mut callback);
Ok(())
}
// TODO(pcwalton): Do some caching, perhaps?
// TODO(pcwalton): Compute this at the same time as `for_each_point`, perhaps?
pub fn glyph_bounds(&self, glyph_id: u16) -> Result<GlyphBounds, FontError> {
let mut bounds = GlyphBounds::default();
try!(self.for_each_point(glyph_id, |point| {
bounds.left = cmp::min(bounds.left, point.position.x as i32);
bounds.bottom = cmp::min(bounds.bottom, point.position.y as i32);
bounds.right = cmp::max(bounds.right, point.position.x as i32);
bounds.top = cmp::max(bounds.top, point.position.y as i32);
}));
Ok(bounds)
}
}
// Moves the reader to the location of the given element in the index. Returns the length of the
// element if the element was found or `None` otherwise.
fn find_in_index(reader: &mut &[u8], index: u16) -> Result<Option<u32>, FontError> {
let count = try!(reader.read_u16::<BigEndian>().map_err(FontError::eof));
if count == 0 {
return Ok(None)
}
let off_size = try!(reader.read_u8().map_err(FontError::eof));
let mut offset_reader = *reader;
try!(offset_reader.jump(off_size as usize * cmp::min(index, count) as usize)
.map_err(FontError::eof));
let offset = try!(read_offset(&mut offset_reader, off_size));
let next_offset = if index < count {
Some(try!(read_offset(&mut offset_reader, off_size)) - offset)
} else {
None
};
try!(reader.jump(off_size as usize * (count as usize + 1) + offset as usize - 1)
.map_err(FontError::eof));
return Ok(next_offset)
}
// Skips over an INDEX by reading the last element in the offset array and seeking the appropriate
// number of bytes forward.
fn skip_index(reader: &mut &[u8]) -> Result<(), FontError> {
find_in_index(reader, u16::MAX).map(drop)
}
// Returns the integer with the given operator.
fn get_integer_in_dict(reader: &mut &[u8], operator: u16) -> Result<i32, FontError> {
let mut last_integer_operand = None;
loop {
let b0 = try!(reader.read_u8().map_err(FontError::eof));
match b0 {
32...246 => last_integer_operand = Some(b0 as i32 - 139),
247...250 => {
let b1 = try!(reader.read_u8().map_err(FontError::eof));
last_integer_operand = Some((b0 as i32 - 247) * 256 + b1 as i32 + 108)
}
251...254 => {
let b1 = try!(reader.read_u8().map_err(FontError::eof));
last_integer_operand = Some(-(b0 as i32 - 251) * 256 - b1 as i32 - 108)
}
28 => {
last_integer_operand =
Some(try!(reader.read_i16::<BigEndian>().map_err(FontError::eof)) as i32)
}
29 => {
last_integer_operand =
Some(try!(reader.read_i32::<BigEndian>().map_err(FontError::eof)) as i32)
}
30 => {
// TODO(pcwalton): Real numbers.
while (try!(reader.read_u8().map_err(FontError::eof)) & 0xf) != 0xf {}
}
12 => {
let b1 = try!(reader.read_u8().map_err(FontError::eof));
if operator == (((b1 as u16) << 8) | (b0 as u16)) {
match last_integer_operand {
Some(last_integer_operand) => return Ok(last_integer_operand),
None => return Err(FontError::CffIntegerNotFound),
}
}
last_integer_operand = None
}
_ => {
if operator == b0 as u16 {
match last_integer_operand {
Some(last_integer_operand) => return Ok(last_integer_operand),
None => return Err(FontError::CffIntegerNotFound),
}
}
last_integer_operand = None
}
}
}
}
// Reads an Offset with the given size.
fn read_offset(reader: &mut &[u8], size: u8) -> Result<u32, FontError> {
match size {
1 => Ok(try!(reader.read_u8().map_err(FontError::eof)) as u32),
2 => Ok(try!(reader.read_u16::<BigEndian>().map_err(FontError::eof)) as u32),
3 => {
let hi = try!(reader.read_u8().map_err(FontError::eof)) as u32;
let lo = try!(reader.read_u16::<BigEndian>().map_err(FontError::eof)) as u32;
Ok((hi << 16) | lo)
}
4 => Ok(try!(reader.read_u32::<BigEndian>().map_err(FontError::eof))),
_ => Err(FontError::CffBadOffset),
}
}
// The CFF evaluation stack used during CharString reading.
struct EvaluationStack {
array: [i32; 48],
size: u8,
}
impl EvaluationStack {
fn new() -> EvaluationStack {
EvaluationStack {
array: [0; 48],
size: 0,
}
}
fn push(&mut self, value: i32) -> Result<(), FontError> {
if (self.size as usize) < self.array.len() {
self.array[self.size as usize] = value;
self.size += 1;
Ok(())
} else {
Err(FontError::CffStackOverflow)
}
}
fn clear(&mut self) {
self.size = 0
}
}
fn close_path_if_necessary<F>(start: &Point2D<i16>, index_in_contour: u16, mut callback: F)
where F: FnMut(&Point) {
if index_in_contour == 0 {
// No path to close.
return
}
callback(&Point {
position: *start,
index_in_contour: index_in_contour,
kind: PointKind::OnCurve,
});
}
fn add_curve<F>(dx0: i16, dy0: i16,
dx1: i16, dy1: i16,
dx2: i16, dy2: i16,
pos: &mut Point2D<i16>,
index_in_contour: &mut u16,
mut callback: F)
where F: FnMut(&Point) {
pos.x += dx0;
pos.y += dy0;
callback(&Point {
position: *pos,
index_in_contour: *index_in_contour + 0,
kind: PointKind::FirstCubicControl,
});
pos.x += dx1;
pos.y += dy1;
callback(&Point {
position: *pos,
index_in_contour: *index_in_contour + 1,
kind: PointKind::SecondCubicControl,
});
pos.x += dx2;
pos.y += dy2;
callback(&Point {
position: *pos,
index_in_contour: *index_in_contour + 2,
kind: PointKind::OnCurve,
});
*index_in_contour += 3
}

View File

@ -1,337 +0,0 @@
// Copyright 2017 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use byteorder::{BigEndian, ReadBytesExt};
use charmap::{CodepointRange, GlyphMapping, GlyphRange, MappedGlyphRange};
use error::FontError;
use font::FontTable;
use std::cmp;
use std::mem;
use std::u16;
use util::Jump;
pub const TAG: u32 = ((b'c' as u32) << 24) |
((b'm' as u32) << 16) |
((b'a' as u32) << 8) |
(b'p' as u32);
const PLATFORM_ID_UNICODE: u16 = 0;
const PLATFORM_ID_MICROSOFT: u16 = 3;
const MICROSOFT_ENCODING_ID_UNICODE_BMP: u16 = 1;
const MICROSOFT_ENCODING_ID_UNICODE_UCS4: u16 = 10;
const FORMAT_SEGMENT_MAPPING_TO_DELTA_VALUES: u16 = 4;
const FORMAT_SEGMENTED_COVERAGE: u16 = 12;
const MISSING_GLYPH: u16 = 0;
#[derive(Clone, Copy)]
pub struct CmapTable<'a> {
table: FontTable<'a>,
}
impl<'a> CmapTable<'a> {
pub fn new(table: FontTable) -> CmapTable {
CmapTable {
table: table,
}
}
pub fn glyph_mapping_for_codepoint_ranges(&self, codepoint_ranges: &[CodepointRange])
-> Result<GlyphMapping, FontError> {
let mut cmap_reader = self.table.bytes;
// Check version.
if try!(cmap_reader.read_u16::<BigEndian>().map_err(FontError::eof)) != 0 {
return Err(FontError::UnsupportedCmapVersion)
}
let num_tables = try!(cmap_reader.read_u16::<BigEndian>().map_err(FontError::eof));
// Check platform ID and encoding.
// TODO(pcwalton): Handle more.
let mut table_found = false;
for _ in 0..num_tables {
let platform_id = try!(cmap_reader.read_u16::<BigEndian>().map_err(FontError::eof));
let encoding_id = try!(cmap_reader.read_u16::<BigEndian>().map_err(FontError::eof));
let offset = try!(cmap_reader.read_u32::<BigEndian>().map_err(FontError::eof));
match (platform_id, encoding_id) {
(PLATFORM_ID_UNICODE, _) |
(PLATFORM_ID_MICROSOFT, MICROSOFT_ENCODING_ID_UNICODE_BMP) |
(PLATFORM_ID_MICROSOFT, MICROSOFT_ENCODING_ID_UNICODE_UCS4) => {
// Move to the mapping table.
cmap_reader = self.table.bytes;
try!(cmap_reader.jump(offset as usize).map_err(FontError::eof));
table_found = true;
break
}
_ => {}
}
}
if !table_found {
return Err(FontError::UnsupportedCmapEncoding)
}
// Check the mapping table format.
let format = try!(cmap_reader.read_u16::<BigEndian>().map_err(FontError::eof));
match format {
FORMAT_SEGMENT_MAPPING_TO_DELTA_VALUES => {
self.glyph_mapping_for_codepoint_ranges_segment_mapping_format(cmap_reader,
codepoint_ranges)
}
FORMAT_SEGMENTED_COVERAGE => {
self.glyph_mapping_for_codepoint_ranges_segmented_coverage(cmap_reader,
codepoint_ranges)
}
_ => Err(FontError::UnsupportedCmapFormat),
}
}
fn glyph_mapping_for_codepoint_ranges_segment_mapping_format(
&self,
mut cmap_reader: &[u8],
codepoint_ranges: &[CodepointRange])
-> Result<GlyphMapping, FontError> {
// Read the mapping table header.
let _length = try!(cmap_reader.read_u16::<BigEndian>().map_err(FontError::eof));
let _language = try!(cmap_reader.read_u16::<BigEndian>().map_err(FontError::eof));
let seg_count = try!(cmap_reader.read_u16::<BigEndian>().map_err(FontError::eof)) / 2;
let _search_range = try!(cmap_reader.read_u16::<BigEndian>().map_err(FontError::eof));
let _entry_selector = try!(cmap_reader.read_u16::<BigEndian>().map_err(FontError::eof));
let _range_shift = try!(cmap_reader.read_u16::<BigEndian>().map_err(FontError::eof));
// Set up parallel array pointers.
//
// NB: Microsoft's spec refers to `startCode` and `endCode` as `startCount` and `endCount`
// respectively in a few places. I believe this is a mistake, and `startCode` and `endCode`
// are the correct names.
let (end_codes, mut start_codes) = (cmap_reader, cmap_reader);
try!(start_codes.jump((seg_count as usize + 1) * mem::size_of::<u16>())
.map_err(FontError::eof));
let mut id_deltas = start_codes;
try!(id_deltas.jump(seg_count as usize * mem::size_of::<u16>()).map_err(FontError::eof));
let mut id_range_offsets = id_deltas;
try!(id_range_offsets.jump(seg_count as usize * mem::size_of::<u16>())
.map_err(FontError::eof));
let mut glyph_ids = id_range_offsets;
try!(glyph_ids.jump(seg_count as usize * mem::size_of::<u16>()).map_err(FontError::eof));
// Now perform the lookups.
let mut glyph_mapping = GlyphMapping::new();
for codepoint_range in codepoint_ranges {
let mut codepoint_range = *codepoint_range;
while codepoint_range.end >= codepoint_range.start {
if codepoint_range.start > u16::MAX as u32 {
glyph_mapping.push(MappedGlyphRange {
codepoint_start: codepoint_range.start,
glyphs: GlyphRange {
start: MISSING_GLYPH,
end: MISSING_GLYPH,
},
});
codepoint_range.start += 1;
continue
}
let start_codepoint_range = codepoint_range.start as u16;
let mut end_codepoint_range = codepoint_range.end as u16;
// Binary search to find the segment.
let (mut low, mut high) = (0, seg_count);
let mut segment_index = None;
while low < high {
let mid = (low + high) / 2;
let mut end_code = end_codes;
try!(end_code.jump(mid as usize * 2).map_err(FontError::eof));
let end_code = try!(end_code.read_u16::<BigEndian>().map_err(FontError::eof));
if start_codepoint_range > end_code {
low = mid + 1;
continue
}
let mut start_code = start_codes;
try!(start_code.jump(mid as usize * 2).map_err(FontError::eof));
let start_code = try!(start_code.read_u16::<BigEndian>().map_err(FontError::eof));
if start_codepoint_range < start_code {
high = mid;
continue
}
segment_index = Some(mid);
break
}
let segment_index = match segment_index {
Some(segment_index) => segment_index,
None => {
glyph_mapping.push(MappedGlyphRange {
codepoint_start: codepoint_range.start,
glyphs: GlyphRange {
start: MISSING_GLYPH,
end: MISSING_GLYPH,
},
});
codepoint_range.start += 1;
continue
}
};
// Read out the segment info.
let mut start_code = start_codes;
let mut end_code = end_codes;
let mut id_range_offset = id_range_offsets;
let mut id_delta = id_deltas;
try!(start_code.jump(segment_index as usize * 2).map_err(FontError::eof));
try!(end_code.jump(segment_index as usize * 2).map_err(FontError::eof));
try!(id_range_offset.jump(segment_index as usize * 2).map_err(FontError::eof));
try!(id_delta.jump(segment_index as usize * 2).map_err(FontError::eof));
let start_code = try!(start_code.read_u16::<BigEndian>().map_err(FontError::eof));
let end_code = try!(end_code.read_u16::<BigEndian>().map_err(FontError::eof));
let id_range_offset = try!(id_range_offset.read_u16::<BigEndian>()
.map_err(FontError::eof));
let id_delta = try!(id_delta.read_i16::<BigEndian>().map_err(FontError::eof));
end_codepoint_range = cmp::min(end_codepoint_range, end_code);
codepoint_range.start = (end_codepoint_range + 1) as u32;
let start_code_offset = start_codepoint_range - start_code;
let end_code_offset = end_codepoint_range - start_code;
// If we're direct-mapped (`idRangeOffset` = 0), then try to convert as much of the
// codepoint range as possible to a contiguous glyph range.
if id_range_offset == 0 {
// Microsoft's documentation is contradictory as to whether the code offset or
// the actual code is added to the ID delta here. In reality it seems to be the
// latter.
glyph_mapping.push(MappedGlyphRange {
codepoint_start: start_codepoint_range as u32,
glyphs: GlyphRange {
start: (start_codepoint_range as i16).wrapping_add(id_delta) as u16,
end: (end_codepoint_range as i16).wrapping_add(id_delta) as u16,
},
});
continue
}
// Otherwise, look up the glyphs individually.
for code_offset in start_code_offset..(end_code_offset + 1) {
let mut reader = id_range_offsets;
try!(reader.jump(segment_index as usize * 2 + code_offset as usize * 2 +
id_range_offset as usize).map_err(FontError::eof));
let mut glyph_id = try!(reader.read_u16::<BigEndian>().map_err(FontError::eof));
if glyph_id == 0 {
glyph_mapping.push(MappedGlyphRange {
codepoint_start: start_code as u32 + code_offset as u32,
glyphs: GlyphRange {
start: MISSING_GLYPH,
end: MISSING_GLYPH,
},
})
} else {
glyph_id = (glyph_id as i16).wrapping_add(id_delta) as u16;
glyph_mapping.push(MappedGlyphRange {
codepoint_start: start_code as u32 + code_offset as u32,
glyphs: GlyphRange {
start: glyph_id,
end: glyph_id,
},
})
}
}
}
}
Ok(glyph_mapping)
}
fn glyph_mapping_for_codepoint_ranges_segmented_coverage(&self,
mut cmap_reader: &[u8],
codepoint_ranges: &[CodepointRange])
-> Result<GlyphMapping, FontError> {
let _reserved = try!(cmap_reader.read_u16::<BigEndian>().map_err(FontError::eof));
let _length = try!(cmap_reader.read_u32::<BigEndian>().map_err(FontError::eof));
let _language = try!(cmap_reader.read_u32::<BigEndian>().map_err(FontError::eof));
let num_groups = try!(cmap_reader.read_u32::<BigEndian>().map_err(FontError::eof));
// Now perform the lookups.
let mut glyph_mapping = GlyphMapping::new();
for codepoint_range in codepoint_ranges {
let mut codepoint_range = *codepoint_range;
while codepoint_range.end >= codepoint_range.start {
// Binary search to find the segment.
let (mut low, mut high) = (0, num_groups);
let mut found_segment = None;
while low < high {
let mid = (low + high) / 2;
let mut reader = cmap_reader;
try!(reader.jump(mid as usize * mem::size_of::<[u32; 3]>())
.map_err(FontError::eof));
let segment = Segment {
start_char_code: try!(reader.read_u32::<BigEndian>()
.map_err(FontError::eof)),
end_char_code: try!(reader.read_u32::<BigEndian>()
.map_err(FontError::eof)),
start_glyph_id: try!(reader.read_u32::<BigEndian>()
.map_err(FontError::eof)),
};
if codepoint_range.start < segment.start_char_code {
high = mid
} else if codepoint_range.start > segment.end_char_code {
low = mid + 1
} else {
found_segment = Some(segment);
break
}
}
match found_segment {
None => {
glyph_mapping.push(MappedGlyphRange {
codepoint_start: codepoint_range.start,
glyphs: GlyphRange {
start: MISSING_GLYPH,
end: MISSING_GLYPH,
},
});
codepoint_range.start += 1
}
Some(segment) => {
let end = cmp::min(codepoint_range.end, segment.end_char_code);
glyph_mapping.push(MappedGlyphRange {
codepoint_start: codepoint_range.start,
glyphs: GlyphRange {
start: (segment.start_glyph_id + codepoint_range.start -
segment.start_char_code) as u16,
end: (segment.start_glyph_id + end - segment.start_char_code) as
u16,
},
});
codepoint_range.start = end + 1
}
}
}
}
Ok(glyph_mapping)
}
}
#[derive(Clone, Copy)]
struct Segment {
start_char_code: u32,
end_char_code: u32,
start_glyph_id: u32,
}

View File

@ -1,436 +0,0 @@
// Copyright 2017 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use byteorder::{BigEndian, ReadBytesExt};
use error::FontError;
use euclid::Point2D;
use font::{FontTable, Point, PointKind};
use outline::GlyphBounds;
use std::mem;
use tables::head::HeadTable;
use tables::loca::LocaTable;
use util::{F2DOT14_ONE, F2DOT14_ZERO, F2Dot14, Jump};
pub const TAG: u32 = ((b'g' as u32) << 24) |
((b'l' as u32) << 16) |
((b'y' as u32) << 8) |
(b'f' as u32);
bitflags! {
flags SimpleFlags: u8 {
const ON_CURVE = 1 << 0,
const X_SHORT_VECTOR = 1 << 1,
const Y_SHORT_VECTOR = 1 << 2,
const REPEAT = 1 << 3,
const THIS_X_IS_SAME = 1 << 4,
const THIS_Y_IS_SAME = 1 << 5,
}
}
bitflags! {
flags CompositeFlags: u16 {
const ARG_1_AND_2_ARE_WORDS = 1 << 0,
const ARGS_ARE_XY_VALUES = 1 << 1,
const ROUND_XY_TO_GRID = 1 << 2,
const WE_HAVE_A_SCALE = 1 << 3,
const MORE_COMPONENTS = 1 << 5,
const WE_HAVE_AN_X_AND_Y_SCALE = 1 << 6,
const WE_HAVE_A_TWO_BY_TWO = 1 << 7,
}
}
/// TODO(pcwalton): Add some caching so we don't keep going to the `loca` table all the time.
#[derive(Clone, Copy, Debug)]
pub struct GlyfTable<'a> {
pub table: FontTable<'a>,
}
impl<'a> GlyfTable<'a> {
#[inline]
pub fn new(table: FontTable) -> GlyfTable {
GlyfTable {
table: table,
}
}
pub fn for_each_point<F>(&self,
head_table: &HeadTable,
loca_table: &LocaTable,
glyph_id: u16,
callback: F)
-> Result<(), FontError> where F: FnMut(&Point) {
let mut reader = self.table.bytes;
match try!(loca_table.location_of(head_table, glyph_id)) {
None => {
// No points.
return Ok(())
}
Some(offset) => try!(reader.jump(offset as usize).map_err(FontError::eof)),
}
let glyph_start = reader;
let number_of_contours = try!(reader.read_i16::<BigEndian>().map_err(FontError::eof));
if number_of_contours >= 0 {
self.for_each_point_in_simple_glyph(glyph_start, callback)
} else {
self.for_each_point_in_composite_glyph(glyph_start, head_table, loca_table, callback)
}
}
fn for_each_point_in_simple_glyph<F>(&self, mut reader: &[u8], mut callback: F)
-> Result<(), FontError> where F: FnMut(&Point) {
// Determine how many contours we have.
let number_of_contours = try!(reader.read_i16::<BigEndian>().map_err(FontError::eof));
if number_of_contours == 0 {
return Ok(())
}
// Skip over the rest of the header.
try!(reader.jump(mem::size_of::<i16>() * 4).map_err(FontError::eof));
// Find out how many points we have.
let mut endpoints_reader = reader;
try!(reader.jump(mem::size_of::<u16>() as usize * (number_of_contours as usize - 1))
.map_err(FontError::eof));
let number_of_points = try!(reader.read_u16::<BigEndian>().map_err(FontError::eof)) + 1;
// Skip over hinting instructions.
let instruction_length = try!(reader.read_u16::<BigEndian>().map_err(FontError::eof));
try!(reader.jump(instruction_length as usize).map_err(FontError::eof));
// Find the offsets of the X and Y coordinates.
let flags_reader = reader;
let x_coordinate_length = try!(calculate_size_of_x_coordinates(&mut reader,
number_of_points));
// Set up the streams.
let mut flag_parser = try!(FlagParser::new(flags_reader));
let mut x_coordinate_reader = reader;
try!(reader.jump(x_coordinate_length as usize).map_err(FontError::eof));
let mut y_coordinate_reader = reader;
// Now parse the contours.
let (mut position, mut point_index) = (Point2D::new(0, 0), 0);
for _ in 0..number_of_contours {
let contour_point_count =
try!(endpoints_reader.read_u16::<BigEndian>().map_err(FontError::eof)) -
point_index + 1;
let mut first_on_curve_point = None;
let mut initial_off_curve_point = None;
let mut last_point_was_off_curve = false;
let mut point_index_in_contour = 0;
for _ in 0..contour_point_count {
let flags = SimpleFlags::from_bits_truncate(*flag_parser.current);
try!(flag_parser.next());
let mut delta = Point2D::new(0, 0);
if flags.contains(X_SHORT_VECTOR) {
delta.x = try!(x_coordinate_reader.read_u8().map_err(FontError::eof)) as i16;
if !flags.contains(THIS_X_IS_SAME) {
delta.x = -delta.x
}
} else if !flags.contains(THIS_X_IS_SAME) {
delta.x = try!(x_coordinate_reader.read_i16::<BigEndian>()
.map_err(FontError::eof))
}
if flags.contains(Y_SHORT_VECTOR) {
delta.y = try!(y_coordinate_reader.read_u8().map_err(FontError::eof)) as i16;
if !flags.contains(THIS_Y_IS_SAME) {
delta.y = -delta.y
}
} else if !flags.contains(THIS_Y_IS_SAME) {
delta.y = try!(y_coordinate_reader.read_i16::<BigEndian>()
.map_err(FontError::eof))
}
if last_point_was_off_curve && !flags.contains(ON_CURVE) {
let position = position + delta / 2;
// An important edge case!
if first_on_curve_point.is_none() {
first_on_curve_point = Some(position)
}
callback(&Point {
position: position,
index_in_contour: point_index_in_contour,
kind: PointKind::OnCurve,
});
point_index_in_contour += 1
}
position = position + delta;
if flags.contains(ON_CURVE) && first_on_curve_point.is_none() {
first_on_curve_point = Some(position)
}
// Sometimes the initial point is an off curve point. In that case, save it so we
// can emit it later when closing the path.
if !flags.contains(ON_CURVE) && first_on_curve_point.is_none() {
debug_assert!(initial_off_curve_point.is_none());
initial_off_curve_point = Some(position)
} else {
callback(&Point {
position: position,
kind: if flags.contains(ON_CURVE) {
PointKind::OnCurve
} else {
PointKind::QuadControl
},
index_in_contour: point_index_in_contour,
});
point_index_in_contour += 1
}
last_point_was_off_curve = !flags.contains(ON_CURVE);
point_index += 1;
}
// We're about to close the path. Emit the initial off curve point if there was one.
if let Some(initial_off_curve_point) = initial_off_curve_point {
if last_point_was_off_curve {
// Another important edge case!
let position = position + (initial_off_curve_point - position) / 2;
callback(&Point {
position: position,
index_in_contour: point_index_in_contour,
kind: PointKind::OnCurve,
});
point_index_in_contour += 1
}
callback(&Point {
position: initial_off_curve_point,
kind: PointKind::QuadControl,
index_in_contour: point_index_in_contour,
});
point_index_in_contour += 1
}
// Close the path.
if let Some(first_on_curve_point) = first_on_curve_point {
callback(&Point {
position: first_on_curve_point,
kind: PointKind::OnCurve,
index_in_contour: point_index_in_contour,
})
}
}
Ok(())
}
// TODO(pcwalton): Consider rasterizing pieces of composite glyphs independently and
// compositing them together.
fn for_each_point_in_composite_glyph<F>(&self,
mut reader: &[u8],
head_table: &HeadTable,
loca_table: &LocaTable,
mut callback: F)
-> Result<(), FontError> where F: FnMut(&Point) {
try!(reader.jump(mem::size_of::<i16>() * 5).map_err(FontError::eof));
loop {
let flags = try!(reader.read_u16::<BigEndian>().map_err(FontError::eof));
let flags = CompositeFlags::from_bits_truncate(flags);
let glyph_index = try!(reader.read_u16::<BigEndian>().map_err(FontError::eof));
let (arg0, arg1);
if flags.contains(ARG_1_AND_2_ARE_WORDS) {
arg0 = try!(reader.read_i16::<BigEndian>().map_err(FontError::eof));
arg1 = try!(reader.read_i16::<BigEndian>().map_err(FontError::eof));
} else {
arg0 = try!(reader.read_i8().map_err(FontError::eof)) as i16;
arg1 = try!(reader.read_i8().map_err(FontError::eof)) as i16;
}
let mut transform = Mat3x2::identity();
if flags.contains(ARGS_ARE_XY_VALUES) {
transform.m02 = arg0;
transform.m12 = arg1;
}
if flags.contains(WE_HAVE_A_SCALE) {
let scale = F2Dot14(try!(reader.read_i16::<BigEndian>().map_err(FontError::eof)));
transform.m00 = scale;
transform.m11 = scale;
} else if flags.contains(WE_HAVE_AN_X_AND_Y_SCALE) {
transform.m00 = F2Dot14(try!(reader.read_i16::<BigEndian>()
.map_err(FontError::eof)));
transform.m11 = F2Dot14(try!(reader.read_i16::<BigEndian>()
.map_err(FontError::eof)));
} else if flags.contains(WE_HAVE_A_TWO_BY_TWO) {
transform.m00 = F2Dot14(try!(reader.read_i16::<BigEndian>()
.map_err(FontError::eof)));
transform.m01 = F2Dot14(try!(reader.read_i16::<BigEndian>()
.map_err(FontError::eof)));
transform.m10 = F2Dot14(try!(reader.read_i16::<BigEndian>()
.map_err(FontError::eof)));
transform.m11 = F2Dot14(try!(reader.read_i16::<BigEndian>()
.map_err(FontError::eof)));
}
if let Some(offset) = try!(loca_table.location_of(head_table, glyph_index)) {
let mut reader = self.table.bytes;
try!(reader.jump(offset as usize).map_err(FontError::eof));
try!(self.for_each_point_in_simple_glyph(reader, |point| {
callback(&transform.transform(&point))
}));
}
if !flags.contains(MORE_COMPONENTS) {
break
}
}
Ok(())
}
pub fn glyph_bounds(&self, head_table: &HeadTable, loca_table: &LocaTable, glyph_id: u16)
-> Result<GlyphBounds, FontError> {
let mut reader = self.table.bytes;
match try!(loca_table.location_of(head_table, glyph_id)) {
None => {
// No outlines.
return Ok(GlyphBounds {
left: 0,
bottom: 0,
right: 0,
top: 0,
})
}
Some(offset) => try!(reader.jump(offset as usize).map_err(FontError::eof)),
}
// Skip over the number of contours.
try!(reader.read_i16::<BigEndian>().map_err(FontError::eof));
let x_min = try!(reader.read_i16::<BigEndian>().map_err(FontError::eof));
let y_min = try!(reader.read_i16::<BigEndian>().map_err(FontError::eof));
let x_max = try!(reader.read_i16::<BigEndian>().map_err(FontError::eof));
let y_max = try!(reader.read_i16::<BigEndian>().map_err(FontError::eof));
Ok(GlyphBounds {
left: x_min as i32,
bottom: y_min as i32,
right: x_max as i32,
top: y_max as i32,
})
}
}
// Given a reader pointing to the start of the list of flags, returns the size in bytes of the list
// of X coordinates and positions the reader at the start of that list.
#[inline]
fn calculate_size_of_x_coordinates<'a, 'b>(reader: &'a mut &'b [u8], number_of_points: u16)
-> Result<u16, FontError> {
let (mut x_coordinate_length, mut points_left) = (0, number_of_points);
while points_left > 0 {
let flags = SimpleFlags::from_bits_truncate(try!(reader.read_u8()
.map_err(FontError::eof)));
let repeat_count = if !flags.contains(REPEAT) {
1
} else {
try!(reader.read_u8().map_err(FontError::eof)) as u16 + 1
};
if flags.contains(X_SHORT_VECTOR) {
x_coordinate_length += repeat_count
} else if !flags.contains(THIS_X_IS_SAME) {
x_coordinate_length += repeat_count * 2
}
points_left -= repeat_count
}
Ok(x_coordinate_length)
}
struct FlagParser<'a> {
next: &'a [u8],
current: &'a u8,
repeats_left: u8,
}
impl<'a> FlagParser<'a> {
#[inline]
fn new(buffer: &[u8]) -> Result<FlagParser, FontError> {
let mut parser = FlagParser {
next: buffer,
current: &buffer[0],
repeats_left: 0,
};
try!(parser.next());
Ok(parser)
}
#[inline]
fn next(&mut self) -> Result<(), FontError> {
if self.repeats_left > 0 {
self.repeats_left -= 1;
return Ok(())
}
self.current = match self.next.get(0) {
Some(value) => value,
None => return Err(FontError::UnexpectedEof),
};
let flags = SimpleFlags::from_bits_truncate(*self.current);
self.next = &self.next[1..];
if flags.contains(REPEAT) {
self.repeats_left = match self.next.get(0) {
Some(&value) => value,
None => return Err(FontError::UnexpectedEof),
};
self.next = &self.next[1..];
} else {
self.repeats_left = 0
}
Ok(())
}
}
#[derive(Copy, Clone, Debug)]
struct Mat3x2 {
m00: F2Dot14,
m01: F2Dot14,
m02: i16,
m10: F2Dot14,
m11: F2Dot14,
m12: i16,
}
impl Mat3x2 {
fn identity() -> Mat3x2 {
Mat3x2 {
m00: F2DOT14_ONE, m01: F2DOT14_ZERO, m02: 0,
m10: F2DOT14_ZERO, m11: F2DOT14_ONE, m12: 0,
}
}
// TODO(pcwalton): SIMD/FMA.
fn transform(&self, point: &Point) -> Point {
let p = point.position;
Point {
position: Point2D::new(self.m00 * p.x + self.m01 * p.y + self.m02,
self.m10 * p.x + self.m11 * p.y + self.m12),
..*point
}
}
}

View File

@ -1,84 +0,0 @@
// Copyright 2017 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use byteorder::{BigEndian, ReadBytesExt};
use error::FontError;
use font::FontTable;
use outline::GlyphBounds;
use std::mem;
use util::Jump;
pub const TAG: u32 = ((b'h' as u32) << 24) |
((b'e' as u32) << 16) |
((b'a' as u32) << 8) |
(b'd' as u32);
const MAGIC_NUMBER: u32 = 0x5f0f3cf5;
#[derive(Clone, Debug)]
pub struct HeadTable {
pub units_per_em: u16,
pub index_to_loc_format: i16,
pub max_glyph_bounds: GlyphBounds,
}
impl HeadTable {
pub fn new(table: FontTable) -> Result<HeadTable, FontError> {
let mut reader = table.bytes;
// Check the version.
let major_version = try!(reader.read_u16::<BigEndian>().map_err(FontError::eof));
let minor_version = try!(reader.read_u16::<BigEndian>().map_err(FontError::eof));
if (major_version, minor_version) != (1, 0) {
return Err(FontError::UnsupportedHeadVersion)
}
// Check the magic number.
try!(reader.jump(mem::size_of::<u32>() * 2).map_err(FontError::eof));
let magic_number = try!(reader.read_u32::<BigEndian>().map_err(FontError::eof));
if magic_number != MAGIC_NUMBER {
return Err(FontError::UnknownFormat)
}
// Read the units per em.
try!(reader.jump(mem::size_of::<u16>()).map_err(FontError::eof));
let units_per_em = try!(reader.read_u16::<BigEndian>().map_err(FontError::eof));
// Read the maximum bounds.
try!(reader.jump(mem::size_of::<i64>() * 2).map_err(FontError::eof));
let x_min = try!(reader.read_i16::<BigEndian>().map_err(FontError::eof));
let y_min = try!(reader.read_i16::<BigEndian>().map_err(FontError::eof));
let x_max = try!(reader.read_i16::<BigEndian>().map_err(FontError::eof));
let y_max = try!(reader.read_i16::<BigEndian>().map_err(FontError::eof));
let max_glyph_bounds = GlyphBounds {
left: x_min as i32,
bottom: y_min as i32,
right: x_max as i32,
top: y_max as i32,
};
// Read the index-to-location format.
try!(reader.jump(mem::size_of::<u16>() * 2 + mem::size_of::<i16>())
.map_err(FontError::eof));
let index_to_loc_format = try!(reader.read_i16::<BigEndian>().map_err(FontError::eof));
// Check the glyph data format.
let glyph_data_format = try!(reader.read_i16::<BigEndian>().map_err(FontError::eof));
if glyph_data_format != 0 {
return Err(FontError::UnsupportedGlyphFormat)
}
Ok(HeadTable {
units_per_em: units_per_em,
index_to_loc_format: index_to_loc_format,
max_glyph_bounds: max_glyph_bounds,
})
}
}

View File

@ -1,54 +0,0 @@
// Copyright 2017 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use byteorder::{BigEndian, ReadBytesExt};
use error::FontError;
use font::FontTable;
use std::mem;
use util::Jump;
pub const TAG: u32 = ((b'h' as u32) << 24) |
((b'h' as u32) << 16) |
((b'e' as u32) << 8) |
(b'a' as u32);
#[derive(Clone, Debug)]
pub struct HheaTable {
pub line_gap: i16,
pub number_of_h_metrics: u16,
}
impl HheaTable {
pub fn new(table: FontTable) -> Result<HheaTable, FontError> {
let mut reader = table.bytes;
// Check the version.
let major_version = try!(reader.read_u16::<BigEndian>().map_err(FontError::eof));
let minor_version = try!(reader.read_u16::<BigEndian>().map_err(FontError::eof));
if (major_version, minor_version) != (1, 0) {
return Err(FontError::UnsupportedHheaVersion)
}
// Read the height-related metrics.
let _ascender = try!(reader.read_i16::<BigEndian>().map_err(FontError::eof));
let _descender = try!(reader.read_i16::<BigEndian>().map_err(FontError::eof));
let line_gap = try!(reader.read_i16::<BigEndian>().map_err(FontError::eof));
// Read the number of `hmtx` entries.
try!(reader.jump(mem::size_of::<u16>() * 12).map_err(FontError::eof));
let number_of_h_metrics = try!(reader.read_u16::<BigEndian>().map_err(FontError::eof));
Ok(HheaTable {
line_gap: line_gap,
number_of_h_metrics: number_of_h_metrics,
})
}
}

View File

@ -1,66 +0,0 @@
// Copyright 2017 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use byteorder::{BigEndian, ReadBytesExt};
use error::FontError;
use font::FontTable;
use std::mem;
use tables::hhea::HheaTable;
use util::Jump;
pub const TAG: u32 = ((b'h' as u32) << 24) |
((b'm' as u32) << 16) |
((b't' as u32) << 8) |
(b'x' as u32);
#[derive(Clone, Copy)]
pub struct HmtxTable<'a> {
table: FontTable<'a>,
}
impl<'a> HmtxTable<'a> {
pub fn new(table: FontTable) -> HmtxTable {
HmtxTable {
table: table,
}
}
pub fn metrics_for_glyph(&self, hhea_table: &HheaTable, glyph_id: u16)
-> Result<HorizontalMetrics, FontError> {
let mut reader = self.table.bytes;
// Read the advance width.
let advance_width;
if glyph_id < hhea_table.number_of_h_metrics {
try!(reader.jump(mem::size_of::<u16>() * 2 * glyph_id as usize).map_err(FontError::eof));
advance_width = try!(reader.read_u16::<BigEndian>().map_err(FontError::eof))
} else {
try!(reader.jump(mem::size_of::<u16>() * 2 *
(hhea_table.number_of_h_metrics - 1) as usize).map_err(FontError::eof));
advance_width = try!(reader.read_u16::<BigEndian>().map_err(FontError::eof));
try!(reader.jump(mem::size_of::<i16>() * glyph_id as usize).map_err(FontError::eof));
}
// Read the left-side bearing.
let lsb = try!(reader.read_i16::<BigEndian>().map_err(FontError::eof));
Ok(HorizontalMetrics {
advance_width: advance_width,
lsb: lsb,
})
}
}
#[derive(Clone, Copy, Default, Debug)]
pub struct HorizontalMetrics {
pub advance_width: u16,
pub lsb: i16,
}

View File

@ -1,101 +0,0 @@
// Copyright 2017 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use byteorder::{BigEndian, ReadBytesExt};
use error::FontError;
use font::FontTable;
use std::mem;
use util::Jump;
pub const TAG: u32 = ((b'k' as u32) << 24) |
((b'e' as u32) << 16) |
((b'r' as u32) << 8) |
(b'n' as u32);
bitflags! {
flags Coverage: u16 {
const HORIZONTAL = 1 << 0,
const MINIMUM = 1 << 1,
const CROSS_STREAM = 1 << 2,
const OVERRIDE = 1 << 3,
}
}
#[derive(Clone, Copy)]
pub struct KernTable<'a> {
horizontal_table: &'a [u8],
}
impl<'a> KernTable<'a> {
pub fn new(table: FontTable) -> Result<KernTable, FontError> {
let mut kern_reader = table.bytes;
let version = try!(kern_reader.read_u16::<BigEndian>().map_err(FontError::eof));
if version != 0 {
return Err(FontError::UnknownFormat)
}
let n_tables = try!(kern_reader.read_u16::<BigEndian>().map_err(FontError::eof));
let mut horizontal_table = None;
for _ in 0..n_tables {
let mut table_reader = kern_reader;
let _version = try!(table_reader.read_u16::<BigEndian>().map_err(FontError::eof));
let length = try!(table_reader.read_u16::<BigEndian>().map_err(FontError::eof));
let coverage = try!(table_reader.read_u16::<BigEndian>().map_err(FontError::eof));
let coverage_flags = Coverage::from_bits_truncate(coverage);
if coverage_flags.contains(HORIZONTAL) && !coverage_flags.contains(MINIMUM) &&
!coverage_flags.contains(CROSS_STREAM) && (coverage >> 8) == 0 {
let length = length as usize - mem::size_of::<u16>() * 3;
horizontal_table = Some(&table_reader[0..length]);
break
}
try!(kern_reader.jump(length as usize).map_err(FontError::eof));
}
match horizontal_table {
Some(horizontal_table) => {
Ok(KernTable {
horizontal_table: horizontal_table,
})
}
None => Err(FontError::UnknownFormat),
}
}
pub fn kerning_for_glyph_pair(&self, left_glyph_id: u16, right_glyph_id: u16)
-> Result<i16, FontError> {
let mut table_reader = self.horizontal_table;
let n_pairs = try!(table_reader.read_u16::<BigEndian>().map_err(FontError::eof));
try!(table_reader.jump(mem::size_of::<[u16; 3]>()).map_err(FontError::eof));
let (mut low, mut high) = (0, n_pairs as u32);
while low < high {
let mut reader = table_reader;
let mid = (low + high) / 2;
try!(reader.jump(mid as usize * mem::size_of::<[u16; 3]>()).map_err(FontError::eof));
let left = try!(reader.read_u16::<BigEndian>().map_err(FontError::eof));
let right = try!(reader.read_u16::<BigEndian>().map_err(FontError::eof));
let value = try!(reader.read_i16::<BigEndian>().map_err(FontError::eof));
if left_glyph_id < left || (left_glyph_id == left && right_glyph_id < right) {
high = mid
} else if left_glyph_id > left || (left_glyph_id == left && right_glyph_id > right) {
low = mid + 1
} else {
return Ok(value)
}
}
Ok(0)
}
}

View File

@ -1,63 +0,0 @@
// Copyright 2017 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use byteorder::{BigEndian, ReadBytesExt};
use error::FontError;
use font::FontTable;
use tables::head::HeadTable;
use util::Jump;
pub const TAG: u32 = ((b'l' as u32) << 24) |
((b'o' as u32) << 16) |
((b'c' as u32) << 8) |
(b'a' as u32);
pub struct LocaTable<'a> {
table: FontTable<'a>,
}
impl<'a> LocaTable<'a> {
pub fn new(loca_table: FontTable<'a>) -> Result<LocaTable<'a>, FontError> {
Ok(LocaTable {
table: loca_table,
})
}
pub fn location_of(&self, head_table: &HeadTable, glyph_id: u16)
-> Result<Option<u32>, FontError> {
let mut reader = self.table.bytes;
let (this_location, next_location) = match head_table.index_to_loc_format {
0 => {
try!(reader.jump(glyph_id as usize * 2).map_err(FontError::eof));
let this_location =
try!(reader.read_u16::<BigEndian>().map_err(FontError::eof)) as u32 * 2;
let next_location = match reader.read_u16::<BigEndian>() {
Ok(next_location) => Ok(next_location as u32 * 2),
Err(_) => Err(FontError::UnexpectedEof),
};
(this_location, next_location)
}
1 => {
try!(reader.jump(glyph_id as usize * 4).map_err(FontError::eof));
let this_location = try!(reader.read_u32::<BigEndian>().map_err(FontError::eof));
let next_location = reader.read_u32::<BigEndian>().map_err(FontError::eof);
(this_location, next_location)
}
_ => return Err(FontError::UnknownFormat),
};
if next_location == Ok(this_location) {
Ok(None)
} else {
Ok(Some(this_location))
}
}
}

View File

@ -1,44 +0,0 @@
// Copyright 2017 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! OpenType fonts.
// These tables need no parsing and so don't need separate files.
pub mod cvt {
pub const TAG: u32 = ((b'c' as u32) << 24) |
((b'v' as u32) << 16) |
((b't' as u32) << 8) |
(b' ' as u32);
}
pub mod fpgm {
pub const TAG: u32 = ((b'f' as u32) << 24) |
((b'p' as u32) << 16) |
((b'g' as u32) << 8) |
(b'm' as u32);
}
pub mod prep {
pub const TAG: u32 = ((b'p' as u32) << 24) |
((b'r' as u32) << 16) |
((b'e' as u32) << 8) |
(b'p' as u32);
}
pub mod cff;
pub mod cmap;
pub mod glyf;
pub mod head;
pub mod hhea;
pub mod hmtx;
pub mod kern;
pub mod loca;
pub mod os_2;

View File

@ -1,60 +0,0 @@
// Copyright 2017 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use byteorder::{BigEndian, ReadBytesExt};
use error::FontError;
use font::FontTable;
use std::mem;
use util::Jump;
pub const TAG: u32 = ((b'O' as u32) << 24) |
((b'S' as u32) << 16) |
((b'/' as u32) << 8) |
(b'2' as u32);
#[derive(Clone, Debug)]
pub struct Os2Table {
pub typo_ascender: i16,
pub typo_descender: i16,
pub typo_line_gap: i16,
}
impl Os2Table {
pub fn new(table: FontTable) -> Result<Os2Table, FontError> {
let mut reader = table.bytes;
// We should be compatible with all versions. If this is greater than version 5, follow
// Postel's law and hope for the best.
let version = try!(reader.read_u16::<BigEndian>().map_err(FontError::eof));
// Skip to the line gap.
try!(reader.jump(mem::size_of::<u16>() * 15).map_err(FontError::eof));
try!(reader.jump(10).map_err(FontError::eof));
if version == 0 {
try!(reader.jump(mem::size_of::<u32>() * 2).map_err(FontError::eof));
} else {
try!(reader.jump(mem::size_of::<u32>() * 5).map_err(FontError::eof));
}
try!(reader.jump(mem::size_of::<u16>() * 3).map_err(FontError::eof));
// Read the line spacing information.
let typo_ascender = try!(reader.read_i16::<BigEndian>().map_err(FontError::eof));
let typo_descender = try!(reader.read_i16::<BigEndian>().map_err(FontError::eof));
let typo_line_gap = try!(reader.read_i16::<BigEndian>().map_err(FontError::eof));
Ok(Os2Table {
typo_ascender: typo_ascender,
typo_descender: typo_descender,
typo_line_gap: typo_line_gap,
})
}
}

View File

@ -1,30 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
use charmap::CodepointRange;
use font::Font;
use memmap::{Mmap, Protection};
use outline::OutlineBuilder;
use test::Bencher;
static TEST_FONT_PATH: &'static str = "resources/tests/nimbus-sans/NimbusSanL-Regu.ttf";
#[bench]
fn bench_add_glyphs(bencher: &mut Bencher) {
let file = Mmap::open_path(TEST_FONT_PATH, Protection::Read).expect("Couldn't open test font");
let mut buffer = vec![];
unsafe {
let font = Font::new(file.as_slice(), &mut buffer).unwrap();
let codepoint_ranges = [CodepointRange::new('!' as u32, '~' as u32)];
let glyph_mapping = font.glyph_mapping_for_codepoint_ranges(&codepoint_ranges)
.expect("Couldn't find glyph ranges");
bencher.iter(|| {
let mut outline_builder = OutlineBuilder::new();
for (_, glyph_id) in glyph_mapping.iter() {
outline_builder.add_glyph(&font, glyph_id).unwrap();
}
});
}
}

View File

@ -1,13 +0,0 @@
// Copyright 2017 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
mod buffers;
mod rect_packer;

View File

@ -1,49 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
use rect_packer::RectPacker;
use euclid::{Rect, Size2D};
use std::cmp;
fn pack_objects(available_width: u32, objects: Vec<(u32, u32)>)
-> (RectPacker, Vec<Rect<u32>>, u32) {
let objects: Vec<_> = objects.iter()
.map(|&(width, height)| Size2D::new(width, height))
.collect();
let available_width = 2 +
cmp::max(available_width, objects.iter().map(|object| object.width).max().unwrap_or(0));
let shelf_height = objects.iter().map(|object| object.height).max().unwrap_or(0) + 2;
let mut rect_packer = RectPacker::new(available_width, shelf_height);
let rects = objects.iter()
.map(|object| Rect::new(rect_packer.pack(object).unwrap(), *object))
.collect();
(rect_packer, rects, available_width)
}
quickcheck! {
fn objects_dont_overlap(available_width: u32, objects: Vec<(u32, u32)>) -> bool {
let (_, rects, _) = pack_objects(available_width, objects);
for (i, a) in rects.iter().enumerate() {
for b in &rects[(i + 1)..] {
assert!(!a.intersects(b))
}
}
true
}
fn objects_dont_exceed_available_width(available_width: u32, objects: Vec<(u32, u32)>) -> bool {
let (_, rects, available_width) = pack_objects(available_width, objects);
rects.iter().all(|rect| rect.max_x() <= available_width)
}
fn objects_dont_cross_shelves(available_width: u32, objects: Vec<(u32, u32)>) -> bool {
let (rect_packer, rects, _) = pack_objects(available_width, objects);
rects.iter().all(|rect| {
let shelf_height = rect_packer.shelf_height();
rect.is_empty() || rect.origin.y / shelf_height == (rect.max_y() - 1) / shelf_height
})
}
}

View File

@ -1,230 +0,0 @@
// Copyright 2017 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! Simple text layout.
//!
//! Do not use this for international or high-quality text. This layout has all of the limitations
//! of the shaper; additionally, it only does left-to-right text with a uniform page width and no
//! control over line spacing. Use Cocoa's `NSLayoutManager`, Pango, etc. for real use.
use charmap::CodepointRanges;
use error::GlyphStoreCreationError;
use euclid::{Point2D, Rect};
use font::Font;
use outline::{OutlineBuilder, Outlines};
use shaper;
use std::u16;
#[derive(Clone)]
pub struct Typesetter {
pub glyph_positions: Vec<GlyphPosition>,
page_width: f32,
cursor: Point2D<f32>,
}
impl Typesetter {
pub fn new(page_width: f32, initial_font: &Font, initial_point_size: f32) -> Typesetter {
let pixels_per_unit = initial_point_size / initial_font.units_per_em() as f32;
let initial_position = initial_font.ascender() as f32 * pixels_per_unit;
Typesetter {
glyph_positions: vec![],
page_width: page_width,
cursor: Point2D::new(0.0, initial_position),
}
}
pub fn add_text(&mut self, font: &Font, point_size: f32, string: &str) {
// TODO(pcwalton): Cache this mapping.
let mut chars: Vec<char> = string.chars().collect();
chars.push(' ');
chars.sort();
let codepoint_ranges = CodepointRanges::from_sorted_chars(&chars);
let glyph_mapping = font.glyph_mapping_for_codepoint_ranges(&codepoint_ranges.ranges)
.unwrap();
// All of these values are in pixels.
let pixels_per_unit = point_size / font.units_per_em() as f32;
let space_advance = font.metrics_for_glyph(glyph_mapping.glyph_for(' ' as u32).unwrap())
.unwrap()
.advance_width as f32 * pixels_per_unit;
let line_spacing = (font.ascender() as f32 - font.descender() as f32 +
font.line_gap() as f32) * pixels_per_unit;
for word in string.split_whitespace() {
let shaped_glyph_positions = shaper::shape_text(&font, &glyph_mapping, word);
let total_advance = pixels_per_unit *
shaped_glyph_positions.iter().map(|p| p.advance as f32).sum::<f32>();
if self.cursor.x + total_advance > self.page_width {
self.cursor.x = 0.0;
self.cursor.y += line_spacing;
}
for glyph_position in &shaped_glyph_positions {
self.glyph_positions.push(GlyphPosition {
x: self.cursor.x,
y: self.cursor.y,
glyph_id: glyph_position.glyph_id,
});
self.cursor.x += glyph_position.advance as f32 * pixels_per_unit;
}
self.cursor.x += space_advance
}
}
pub fn glyph_positions(&self) -> &[GlyphPosition] {
&self.glyph_positions
}
pub fn create_glyph_store(&self, font: &Font) -> Result<GlyphStore, GlyphStoreCreationError> {
let glyph_ids = self.glyph_positions
.iter()
.map(|glyph_position| glyph_position.glyph_id)
.collect();
GlyphStore::from_glyph_ids(glyph_ids, font)
}
/// Returns the positions of the glyphs that intersect the given pixel rectangle.
///
/// Requires a `GlyphStore` to be created first.
pub fn positioned_glyphs_in_rect(&self,
bounding_rect: &Rect<f32>,
glyph_store: &GlyphStore,
point_size: f32,
scale: f32,
subpixel_granularity: f32)
-> Vec<PositionedGlyph> {
let subpixel_inv_granularity = 1.0 / subpixel_granularity;
let mut positioned_glyphs = vec![];
for glyph_position in &self.glyph_positions {
// If this glyph is not in the glyph store, just skip it.
//
// TODO(pcwalton): Notify the caller somehow?
let glyph_index = match glyph_store.glyph_index(glyph_position.glyph_id) {
None => continue,
Some(glyph_index) => glyph_index,
};
let mut glyph_subpixel_bounds = glyph_store.outlines.glyph_subpixel_bounds(glyph_index,
point_size);
glyph_subpixel_bounds.scale(scale);
let glyph_pixel_bounds = glyph_subpixel_bounds.round_out();
// Snap the rect to the nearest granule.
let glyph_snapped_origin =
Point2D::new((glyph_position.x * scale * subpixel_inv_granularity).round() *
subpixel_granularity,
((glyph_position.y * scale).round() - glyph_pixel_bounds.top as f32));
let glyph_snapped_rect = Rect::new(glyph_snapped_origin, glyph_subpixel_bounds.size());
debug_assert!(glyph_snapped_rect.origin.y == glyph_snapped_rect.origin.y.round());
if !glyph_snapped_rect.intersects(bounding_rect) {
continue
}
let subpixel_x = if glyph_snapped_origin.x >= 0.0 {
glyph_snapped_origin.x.fract()
} else {
1.0 + glyph_snapped_origin.x.fract()
};
positioned_glyphs.push(PositionedGlyph {
bounds: glyph_snapped_rect,
subpixel_x: subpixel_x,
glyph_index: glyph_index,
})
}
positioned_glyphs
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct GlyphPosition {
pub x: f32,
pub y: f32,
pub glyph_id: u16,
}
impl GlyphPosition {
#[inline]
pub fn position(&self) -> Point2D<f32> {
Point2D::new(self.x, self.y)
}
}
pub struct GlyphStore {
pub outlines: Outlines,
pub glyph_id_to_glyph_index: Vec<u16>,
pub all_glyph_indices: Vec<u16>,
}
impl GlyphStore {
fn from_glyph_ids(mut glyph_ids: Vec<u16>, font: &Font)
-> Result<GlyphStore, GlyphStoreCreationError> {
glyph_ids.sort();
glyph_ids.dedup();
let last_glyph_id = match glyph_ids.last() {
Some(&id) => id + 1,
None => 0,
};
let mut outline_builder = OutlineBuilder::new();
let mut glyph_id_to_glyph_index = vec![u16::MAX; last_glyph_id as usize];
let mut all_glyph_indices = vec![];
for glyph_id in glyph_ids {
let glyph_index = try!(outline_builder.add_glyph(font, glyph_id)
.map_err(GlyphStoreCreationError::FontError));
glyph_id_to_glyph_index[glyph_id as usize] = glyph_index;
all_glyph_indices.push(glyph_index);
}
let outlines = try!(outline_builder.create_buffers()
.map_err(GlyphStoreCreationError::GlError));
all_glyph_indices.sort();
all_glyph_indices.dedup();
Ok(GlyphStore {
outlines: outlines,
glyph_id_to_glyph_index: glyph_id_to_glyph_index,
all_glyph_indices: all_glyph_indices,
})
}
pub fn from_codepoints(codepoints: &CodepointRanges, font: &Font)
-> Result<GlyphStore, GlyphStoreCreationError> {
let mapping = try!(font.glyph_mapping_for_codepoint_ranges(&codepoints.ranges)
.map_err(GlyphStoreCreationError::FontError));
let glyph_ids = mapping.iter().map(|(_, glyph_id)| glyph_id).collect();
GlyphStore::from_glyph_ids(glyph_ids, font)
}
#[inline]
pub fn glyph_index(&self, glyph_id: u16) -> Option<u16> {
match self.glyph_id_to_glyph_index.get(glyph_id as usize) {
None | Some(&u16::MAX) => None,
Some(&index) => Some(index),
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct PositionedGlyph {
pub bounds: Rect<f32>,
pub subpixel_x: f32,
pub glyph_index: u16,
}

View File

@ -1,140 +0,0 @@
// Copyright 2017 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use num_traits::identities::Zero;
use std::ops::{Add, Div, Mul, Neg, Sub};
pub const F26DOT6_ZERO: F26Dot6 = F26Dot6(0);
pub const F2DOT14_ZERO: F2Dot14 = F2Dot14(0);
pub const F2DOT14_ONE: F2Dot14 = F2Dot14(1 << 14);
/// 26.6 fixed point.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct F26Dot6(pub i32);
impl Zero for F26Dot6 {
#[inline]
fn zero() -> F26Dot6 {
F26DOT6_ZERO
}
#[inline]
fn is_zero(&self) -> bool {
*self == F26DOT6_ZERO
}
}
impl Add<F26Dot6> for F26Dot6 {
type Output = F26Dot6;
#[inline]
fn add(self, other: F26Dot6) -> F26Dot6 {
F26Dot6(self.0 + other.0)
}
}
impl Sub<F26Dot6> for F26Dot6 {
type Output = F26Dot6;
#[inline]
fn sub(self, other: F26Dot6) -> F26Dot6 {
F26Dot6(self.0 - other.0)
}
}
impl Mul<F26Dot6> for F26Dot6 {
type Output = F26Dot6;
#[inline]
fn mul(self, other: F26Dot6) -> F26Dot6 {
F26Dot6(((self.0 as i64 * other.0 as i64 + 1 << 5) >> 6) as i32)
}
}
impl Div<F26Dot6> for F26Dot6 {
type Output = F26Dot6;
#[inline]
fn div(self, other: F26Dot6) -> F26Dot6 {
F26Dot6((((self.0 as i64) << 6) / other.0 as i64) as i32)
}
}
impl Neg for F26Dot6 {
type Output = F26Dot6;
#[inline]
fn neg(self) -> F26Dot6 {
F26Dot6(-self.0)
}
}
/// 2.14 fixed point.
#[derive(Copy, Clone, PartialEq, Debug)]
pub struct F2Dot14(pub i16);
impl Zero for F2Dot14 {
#[inline]
fn zero() -> F2Dot14 {
F2DOT14_ZERO
}
#[inline]
fn is_zero(&self) -> bool {
*self == F2DOT14_ZERO
}
}
impl Add<F2Dot14> for F2Dot14 {
type Output = F2Dot14;
#[inline]
fn add(self, other: F2Dot14) -> F2Dot14 {
F2Dot14(self.0 + other.0)
}
}
impl Mul<i16> for F2Dot14 {
type Output = i16;
#[inline]
fn mul(self, other: i16) -> i16 {
((self.0 as i32 * other as i32) >> 14) as i16
}
}
impl Neg for F2Dot14 {
type Output = F2Dot14;
#[inline]
fn neg(self) -> F2Dot14 {
F2Dot14(-self.0)
}
}
/// A faster version of `Seek` that supports only forward motion from the current position.
pub trait Jump {
/// Moves the pointer forward `n` bytes from the *current* position.
fn jump(&mut self, n: usize) -> Result<(), ()>;
}
impl<'a> Jump for &'a [u8] {
#[inline]
fn jump(&mut self, n: usize) -> Result<(), ()> {
if n <= self.len() {
*self = &(*self)[n..];
Ok(())
} else {
Err(())
}
}
}