| /* |
| * Copyright 2025 The Android Open Source Project |
| * |
| * 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. |
| */ |
| |
| use anyhow::{bail, Result}; |
| use atrace::AtraceTag; |
| use native_activity_thread_bindgen::ashmem_get_size_region; |
| use nix::sys::mman; |
| use std::{ |
| convert::TryFrom, |
| os::fd::{AsRawFd, OwnedFd}, |
| ptr::NonNull, |
| sync::OnceLock, |
| }; |
| |
| static DEFAULT_FONT_FAMILY: &str = "sans-serif"; |
| |
| struct Ashmem { |
| ptr: NonNull<libc::c_void>, |
| size: usize, |
| } |
| |
| // SAFETY: `self.ptr` is mmap'ed and is valid across the entire process address space. |
| unsafe impl Send for Ashmem {} |
| |
| impl TryFrom<OwnedFd> for Ashmem { |
| type Error = anyhow::Error; |
| |
| fn try_from(fd: OwnedFd) -> Result<Self> { |
| // SAFETY: The passed fd is valid. |
| let size = unsafe { ashmem_get_size_region(fd.as_raw_fd()) }; |
| if size < 0 { |
| bail!("Failed to get the size of system font map ashmem region") |
| } |
| let size = size as usize; |
| // SAFETY: The passed fd and length are valid. |
| let ptr = unsafe { |
| mman::mmap( |
| None, |
| size.try_into().unwrap(), |
| mman::ProtFlags::PROT_READ, |
| mman::MapFlags::MAP_SHARED, |
| fd, |
| 0, |
| )? |
| }; |
| Ok(Self { ptr, size }) |
| } |
| } |
| |
| impl Drop for Ashmem { |
| fn drop(&mut self) { |
| // SAFETY: `self.ptr` points to memory returned from `mmap` and `self.size` is the length of |
| // the mapped memory. |
| let _ = unsafe { mman::munmap(self.ptr, self.size) }; |
| } |
| } |
| |
| impl AsRef<[u8]> for Ashmem { |
| fn as_ref(&self) -> &[u8] { |
| // SAFETY: `self.ptr` is returned from mmap(2) thus addr points to `self.size` consecutive |
| // bytes of valid memory. |
| unsafe { std::slice::from_raw_parts(self.ptr.as_ptr() as *const u8, self.size) } |
| } |
| } |
| |
| struct SystemFontMap(Ashmem); |
| |
| // SAFETY: System font map is only set once and is immutable after that. |
| unsafe impl Sync for SystemFontMap {} |
| |
| // The mmap'ed regions of the system font map needs to be valid for the entire process lifetime as |
| // some data structures in `libminikin` directly point to it. |
| static SYSTEM_FONT_MAP: OnceLock<SystemFontMap> = OnceLock::new(); |
| |
| fn set_system_font_map(fd: OwnedFd) -> Result<&'static SystemFontMap> { |
| let ashmem = Ashmem::try_from(fd)?; |
| SYSTEM_FONT_MAP |
| .set(SystemFontMap(ashmem)) |
| .map_err(|_| anyhow::anyhow!("Cannot update system font map once set"))?; |
| Ok(SYSTEM_FONT_MAP.get().expect("system font map not set")) |
| } |
| |
| struct SystemFontMapReader { |
| data: &'static [u8], |
| position: usize, |
| } |
| |
| impl SystemFontMapReader { |
| fn new(system_font_map: &'static SystemFontMap) -> Self { |
| Self { data: system_font_map.0.as_ref(), position: 0 } |
| } |
| |
| fn remaining_slice(&self) -> &[u8] { |
| &self.data[self.position..] |
| } |
| |
| fn consume(&mut self, size: usize) { |
| self.position += size; |
| } |
| |
| // Consumes minikin::FontStyle |
| fn consume_font_style(&mut self) { |
| // mWeight |
| self.consume(std::mem::size_of::<u16>()); |
| // mSlant |
| self.consume(std::mem::size_of::<u8>()); |
| } |
| |
| // Consumes bytes for android::Typeface |
| fn consume_type_face(&mut self) { |
| self.consume_font_style(); |
| // android::Typeface::Style |
| self.consume(std::mem::size_of::<u8>()); |
| // Typeface base weight |
| self.consume(std::mem::size_of::<i32>()); |
| } |
| |
| fn read_four_bytes(&mut self) -> [u8; 4] { |
| let size = std::mem::size_of::<u32>(); |
| let bytes = self.remaining_slice()[..size].try_into().unwrap(); |
| self.consume(size); |
| bytes |
| } |
| |
| fn read_be_i32(&mut self) -> i32 { |
| i32::from_be_bytes(self.read_four_bytes()) |
| } |
| |
| fn read_le_u32(&mut self) -> u32 { |
| u32::from_le_bytes(self.read_four_bytes()) |
| } |
| |
| fn read_be_u32(&mut self) -> u32 { |
| u32::from_be_bytes(self.read_four_bytes()) |
| } |
| |
| fn read_string(&mut self) -> Result<String> { |
| let len = self.read_be_u32() as usize; |
| let bytes = self.remaining_slice()[..len].to_vec(); |
| self.consume(len); |
| Ok(String::from_utf8(bytes)?) |
| } |
| } |
| |
| pub fn load_system_font_map(fd: OwnedFd) -> Result<()> { |
| atrace::trace_method!(AtraceTag::Graphics); |
| |
| let system_font_map = set_system_font_map(fd)?; |
| let mut reader = SystemFontMapReader::new(system_font_map); |
| let typeface_bytes_count = reader.read_be_i32(); |
| |
| ffi::minikin_font_skia_factory_init(); |
| let mut bytes_read = 0usize; |
| let collections = |
| ffi::font_collection_read_vector_slice(reader.remaining_slice(), &mut bytes_read); |
| reader.consume(bytes_read); |
| |
| let typeface_count = reader.read_le_u32(); |
| let mut font_collections = Vec::with_capacity(typeface_count as usize); |
| for _ in 0..typeface_count { |
| let idx = reader.read_le_u32(); |
| reader.consume_type_face(); |
| font_collections.push(ffi::vector_get(&collections, idx as usize)); |
| } |
| |
| if typeface_bytes_count as usize != reader.position { |
| bail!( |
| "Typeface bytes count does not match: expected {} but got {}", |
| typeface_bytes_count, |
| reader.position |
| ); |
| } |
| |
| for font_collection in font_collections { |
| let name = reader.read_string()?; |
| if name == DEFAULT_FONT_FAMILY { |
| ffi::system_fonts_register_default(font_collection.clone()); |
| } |
| if matches!( |
| name.as_str(), |
| "serif" | "sans-serif" | "cursive" | "fantasy" | "monospace" | "system-ui" |
| ) { |
| cxx::let_cxx_string!(cname = name); |
| ffi::system_fonts_register_fallback(&cname, font_collection.clone()); |
| } |
| ffi::system_fonts_add_font_map(font_collection); |
| } |
| Ok(()) |
| } |
| |
| #[cxx::bridge] |
| pub mod ffi { |
| #[namespace = "minikin"] |
| unsafe extern "C++" { |
| include!("SystemFontsBridge/SystemFontsBridge.h"); |
| |
| type FontCollection; |
| type FontCollectionPtrs; |
| |
| fn font_collection_read_vector_slice( |
| data: &[u8], |
| bytes_read: &mut usize, |
| ) -> UniquePtr<FontCollectionPtrs>; |
| fn vector_get(collections: &FontCollectionPtrs, index: usize) -> SharedPtr<FontCollection>; |
| |
| fn system_fonts_add_font_map(collection: SharedPtr<FontCollection>); |
| fn system_fonts_register_default(collection: SharedPtr<FontCollection>); |
| fn system_fonts_register_fallback( |
| family_name: &CxxString, |
| collection: SharedPtr<FontCollection>, |
| ); |
| } |
| |
| #[namespace = "android"] |
| unsafe extern "C++" { |
| include!("SystemFontsBridge/SystemFontsBridge.h"); |
| |
| fn minikin_font_skia_factory_init(); |
| } |
| } |