Remove Pathfinder 1 now that Pathfinder 2 supports all its features and
more
This commit is contained in:
parent
c8284e18b2
commit
3352f869f0
|
@ -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"
|
||||
|
|
@ -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.
|
|
@ -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.
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
#version 330
|
||||
|
||||
uniform vec3 uColor;
|
||||
|
||||
out vec4 oFragColor;
|
||||
|
||||
void main() {
|
||||
oFragColor = vec4(uColor, 1.0f);
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
#version 330
|
||||
|
||||
in vec2 aPosition;
|
||||
|
||||
void main() {
|
||||
gl_Position = vec4(aPosition, 0.0f, 1.0f);
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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.
|
Binary file not shown.
|
@ -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
|
|
@ -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,
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue