aboutsummaryrefslogtreecommitdiffstats
path: root/components/fonts/platform/windows/font.rs
diff options
context:
space:
mode:
Diffstat (limited to 'components/fonts/platform/windows/font.rs')
-rw-r--r--components/fonts/platform/windows/font.rs276
1 files changed, 276 insertions, 0 deletions
diff --git a/components/fonts/platform/windows/font.rs b/components/fonts/platform/windows/font.rs
new file mode 100644
index 00000000000..8dd2d78bb1f
--- /dev/null
+++ b/components/fonts/platform/windows/font.rs
@@ -0,0 +1,276 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+// NOTE: https://www.chromium.org/directwrite-font-proxy has useful
+// information for an approach that we'll likely need to take when the
+// renderer moves to a sandboxed process.
+
+use std::cmp::{max, min};
+use std::fmt;
+use std::io::Cursor;
+use std::ops::Deref;
+use std::sync::Arc;
+
+use app_units::Au;
+use dwrote::{FontFace, FontFile};
+use log::{debug, warn};
+use style::computed_values::font_stretch::T as StyleFontStretch;
+use style::computed_values::font_weight::T as StyleFontWeight;
+use style::values::computed::font::FontStyle as StyleFontStyle;
+use truetype::tables::WindowsMetrics;
+use truetype::value::Read;
+use webrender_api::FontInstanceFlags;
+
+use crate::{
+ ot_tag, FontIdentifier, FontMetrics, FontTableMethods, FontTableTag, FontTemplateDescriptor,
+ FractionalPixel, GlyphId, PlatformFontMethods,
+};
+
+// 1em = 12pt = 16px, assuming 72 points per inch and 96 px per inch
+fn pt_to_px(pt: f64) -> f64 {
+ pt / 72. * 96.
+}
+fn em_to_px(em: f64) -> f64 {
+ em * 16.
+}
+fn au_from_em(em: f64) -> Au {
+ Au::from_f64_px(em_to_px(em))
+}
+fn au_from_pt(pt: f64) -> Au {
+ Au::from_f64_px(pt_to_px(pt))
+}
+
+pub struct FontTable {
+ data: Vec<u8>,
+}
+
+impl FontTable {
+ pub fn wrap(data: &[u8]) -> FontTable {
+ FontTable {
+ data: data.to_vec(),
+ }
+ }
+}
+
+impl FontTableMethods for FontTable {
+ fn buffer(&self) -> &[u8] {
+ &self.data
+ }
+}
+
+#[derive(Debug)]
+pub struct PlatformFont {
+ face: Nondebug<FontFace>,
+ /// A reference to this data used to create this [`PlatformFont`], ensuring the
+ /// data stays alive of the lifetime of this struct.
+ _data: Arc<Vec<u8>>,
+ em_size: f32,
+ du_to_px: f32,
+ scaled_du_to_px: f32,
+}
+
+// Based on information from the Skia codebase, it seems that DirectWrite APIs from
+// Windows 10 and beyond are thread safe. If problems arise from this, we can protect the
+// platform font with a Mutex.
+// See https://source.chromium.org/chromium/chromium/src/+/main:third_party/skia/src/ports/SkScalerContext_win_dw.cpp;l=56;bpv=0;bpt=1.
+unsafe impl Sync for PlatformFont {}
+unsafe impl Send for PlatformFont {}
+
+struct Nondebug<T>(T);
+
+impl<T> fmt::Debug for Nondebug<T> {
+ fn fmt(&self, _f: &mut fmt::Formatter) -> fmt::Result {
+ Ok(())
+ }
+}
+
+impl<T> Deref for Nondebug<T> {
+ type Target = T;
+ fn deref(&self) -> &T {
+ &self.0
+ }
+}
+
+impl PlatformFontMethods for PlatformFont {
+ fn new_from_data(
+ _font_identifier: FontIdentifier,
+ data: Arc<Vec<u8>>,
+ face_index: u32,
+ pt_size: Option<Au>,
+ ) -> Result<Self, &'static str> {
+ let font_file = FontFile::new_from_data(data.clone()).ok_or("Could not create FontFile")?;
+ let face = font_file
+ .create_face(face_index, dwrote::DWRITE_FONT_SIMULATIONS_NONE)
+ .map_err(|_| "Could not create FontFace")?;
+
+ let pt_size = pt_size.unwrap_or(au_from_pt(12.));
+ let du_per_em = face.metrics().metrics0().designUnitsPerEm as f32;
+
+ let em_size = pt_size.to_f32_px() / 16.;
+ let design_units_per_pixel = du_per_em / 16.;
+
+ let design_units_to_pixels = 1. / design_units_per_pixel;
+ let scaled_design_units_to_pixels = em_size / design_units_per_pixel;
+
+ Ok(PlatformFont {
+ face: Nondebug(face),
+ _data: data,
+ em_size,
+ du_to_px: design_units_to_pixels,
+ scaled_du_to_px: scaled_design_units_to_pixels,
+ })
+ }
+
+ fn descriptor(&self) -> FontTemplateDescriptor {
+ // We need the font (DWriteFont) in order to be able to query things like
+ // the family name, face name, weight, etc. On Windows 10, the
+ // DWriteFontFace3 interface provides this on the FontFace, but that's only
+ // available on Win10+.
+ //
+ // Instead, we do the parsing work using the truetype crate for raw fonts.
+ // We're just extracting basic info, so this is sufficient for now.
+ //
+ // The `dwrote` APIs take SFNT table tags in a reversed byte order, which
+ // is why `u32::swap_bytes()` is called here.
+ let windows_metrics_bytes = self
+ .face
+ .get_font_table(u32::swap_bytes(ot_tag!('O', 'S', '/', '2')));
+ if windows_metrics_bytes.is_none() {
+ warn!("Could not find OS/2 table in font.");
+ return FontTemplateDescriptor::default();
+ }
+
+ let mut cursor = Cursor::new(windows_metrics_bytes.as_ref().unwrap());
+ let Ok(table) = WindowsMetrics::read(&mut cursor) else {
+ warn!("Could not read OS/2 table in font.");
+ return FontTemplateDescriptor::default();
+ };
+
+ let (weight_val, width_val, italic_bool) = match table {
+ WindowsMetrics::Version0(ref m) => {
+ (m.weight_class, m.width_class, m.selection_flags.0 & 1 == 1)
+ },
+ WindowsMetrics::Version1(ref m) => {
+ (m.weight_class, m.width_class, m.selection_flags.0 & 1 == 1)
+ },
+ WindowsMetrics::Version2(ref m) |
+ WindowsMetrics::Version3(ref m) |
+ WindowsMetrics::Version4(ref m) => {
+ (m.weight_class, m.width_class, m.selection_flags.0 & 1 == 1)
+ },
+ WindowsMetrics::Version5(ref m) => {
+ (m.weight_class, m.width_class, m.selection_flags.0 & 1 == 1)
+ },
+ };
+
+ let weight = StyleFontWeight::from_float(weight_val as f32);
+ let stretch = match min(9, max(1, width_val)) {
+ 1 => StyleFontStretch::ULTRA_CONDENSED,
+ 2 => StyleFontStretch::EXTRA_CONDENSED,
+ 3 => StyleFontStretch::CONDENSED,
+ 4 => StyleFontStretch::SEMI_CONDENSED,
+ 5 => StyleFontStretch::NORMAL,
+ 6 => StyleFontStretch::SEMI_EXPANDED,
+ 7 => StyleFontStretch::EXPANDED,
+ 8 => StyleFontStretch::EXTRA_EXPANDED,
+ 9 => StyleFontStretch::ULTRA_CONDENSED,
+ _ => {
+ warn!("Unknown stretch size.");
+ StyleFontStretch::NORMAL
+ },
+ };
+
+ let style = if italic_bool {
+ StyleFontStyle::ITALIC
+ } else {
+ StyleFontStyle::NORMAL
+ };
+
+ FontTemplateDescriptor::new(weight, stretch, style)
+ }
+
+ fn glyph_index(&self, codepoint: char) -> Option<GlyphId> {
+ let glyph = self.face.get_glyph_indices(&[codepoint as u32])[0];
+ if glyph == 0 {
+ return None;
+ }
+ Some(glyph as GlyphId)
+ }
+
+ fn glyph_h_advance(&self, glyph: GlyphId) -> Option<FractionalPixel> {
+ if glyph == 0 {
+ return None;
+ }
+
+ let gm = self.face.get_design_glyph_metrics(&[glyph as u16], false)[0];
+ let f = (gm.advanceWidth as f32 * self.scaled_du_to_px) as FractionalPixel;
+
+ Some(f)
+ }
+
+ /// Can this font do basic horizontal LTR shaping without Harfbuzz?
+ fn can_do_fast_shaping(&self) -> bool {
+ // TODO copy CachedKernTable from the MacOS X implementation to
+ // somehwere global and use it here. We could also implement the
+ // IDirectWriteFontFace1 interface and use the glyph kerning pair
+ // methods there.
+ false
+ }
+
+ fn glyph_h_kerning(&self, _: GlyphId, _: GlyphId) -> FractionalPixel {
+ 0.0
+ }
+
+ fn metrics(&self) -> FontMetrics {
+ let dm = self.face.metrics().metrics0();
+
+ let au_from_du = |du| -> Au { Au::from_f32_px(du as f32 * self.du_to_px) };
+ let au_from_du_s = |du| -> Au { Au::from_f32_px(du as f32 * self.scaled_du_to_px) };
+
+ // anything that we calculate and don't just pull out of self.face.metrics
+ // is pulled out here for clarity
+ let leading = dm.ascent - dm.capHeight;
+
+ let zero_horizontal_advance = self
+ .glyph_index('0')
+ .and_then(|idx| self.glyph_h_advance(idx))
+ .map(Au::from_f64_px);
+ let ic_horizontal_advance = self
+ .glyph_index('\u{6C34}')
+ .and_then(|idx| self.glyph_h_advance(idx))
+ .map(Au::from_f64_px);
+
+ let metrics = FontMetrics {
+ underline_size: au_from_du(dm.underlineThickness as i32),
+ underline_offset: au_from_du_s(dm.underlinePosition as i32),
+ strikeout_size: au_from_du(dm.strikethroughThickness as i32),
+ strikeout_offset: au_from_du_s(dm.strikethroughPosition as i32),
+ leading: au_from_du_s(leading as i32),
+ x_height: au_from_du_s(dm.xHeight as i32),
+ em_size: au_from_em(self.em_size as f64),
+ ascent: au_from_du_s(dm.ascent as i32),
+ descent: au_from_du_s(dm.descent as i32),
+ max_advance: au_from_pt(0.0), // FIXME
+ average_advance: au_from_pt(0.0), // FIXME
+ line_gap: au_from_du_s((dm.ascent + dm.descent + dm.lineGap as u16) as i32),
+ zero_horizontal_advance,
+ ic_horizontal_advance,
+ };
+ debug!("Font metrics (@{} pt): {:?}", self.em_size * 12., metrics);
+ metrics
+ }
+
+ fn table_for_tag(&self, tag: FontTableTag) -> Option<FontTable> {
+ // dwrote (and presumably the Windows APIs) accept a reversed version of the table
+ // tag bytes, which means that `u32::swap_bytes` must be called here in order to
+ // use a byte order compatible with the rest of Servo.
+ self.face
+ .get_font_table(u32::swap_bytes(tag))
+ .map(|bytes| FontTable { data: bytes })
+ }
+
+ fn webrender_font_instance_flags(&self) -> FontInstanceFlags {
+ FontInstanceFlags::empty()
+ }
+}