| 1 | // Copyright © SixtyFPS GmbH <info@slint.dev> |
| 2 | // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 |
| 3 | |
| 4 | #![doc = include_str!("README.md" )] |
| 5 | #![doc (html_logo_url = "https://slint.dev/logo/slint-logo-square-light.svg" )] |
| 6 | // It would be nice to keep the compiler free of unsafe code |
| 7 | #![deny (unsafe_code)] |
| 8 | |
| 9 | #[cfg (feature = "proc_macro_span" )] |
| 10 | extern crate proc_macro; |
| 11 | |
| 12 | use core::future::Future; |
| 13 | use core::pin::Pin; |
| 14 | use std::cell::RefCell; |
| 15 | use std::collections::HashMap; |
| 16 | use std::rc::Rc; |
| 17 | #[cfg (feature = "software-renderer" )] |
| 18 | use std::sync::Arc; |
| 19 | |
| 20 | pub mod builtin_macros; |
| 21 | pub mod diagnostics; |
| 22 | pub mod embedded_resources; |
| 23 | pub mod expression_tree; |
| 24 | pub mod fileaccess; |
| 25 | pub mod generator; |
| 26 | pub mod langtype; |
| 27 | pub mod layout; |
| 28 | pub mod lexer; |
| 29 | pub mod literals; |
| 30 | pub mod llr; |
| 31 | pub(crate) mod load_builtins; |
| 32 | pub mod lookup; |
| 33 | pub mod namedreference; |
| 34 | pub mod object_tree; |
| 35 | pub mod parser; |
| 36 | pub mod pathutils; |
| 37 | #[cfg (feature = "bundle-translations" )] |
| 38 | pub mod translations; |
| 39 | pub mod typeloader; |
| 40 | pub mod typeregister; |
| 41 | |
| 42 | pub mod passes; |
| 43 | |
| 44 | use crate::generator::OutputFormat; |
| 45 | use std::path::Path; |
| 46 | |
| 47 | /// Specify how the resources are embedded by the compiler |
| 48 | #[derive (Clone, Copy, Debug, Eq, PartialEq)] |
| 49 | pub enum EmbedResourcesKind { |
| 50 | /// Embeds nothing (only useful for interpreter) |
| 51 | Nothing, |
| 52 | /// Only embed builtin resources |
| 53 | OnlyBuiltinResources, |
| 54 | /// Do not embed resources, but list them in the Document as it they were embedded |
| 55 | ListAllResources, |
| 56 | /// Embed all images resources (the content of their files) |
| 57 | EmbedAllResources, |
| 58 | #[cfg (feature = "software-renderer" )] |
| 59 | /// Embed raw texture (process images and fonts) |
| 60 | EmbedTextures, |
| 61 | } |
| 62 | |
| 63 | #[derive (Clone, Debug, Eq, PartialEq, Default)] |
| 64 | #[non_exhaustive ] |
| 65 | pub enum ComponentSelection { |
| 66 | /// All components that inherit from Window. |
| 67 | /// |
| 68 | /// Note: Components marked for export but lacking Window inheritance are not selected (this will produce a warning), |
| 69 | /// For compatibility reason, the last exported component is still selected even if it doesn't inherit Window, |
| 70 | /// and if no component is exported, the last component is selected |
| 71 | #[default] |
| 72 | ExportedWindows, |
| 73 | |
| 74 | /// The Last component (legacy for the viewer / interpreter) |
| 75 | /// |
| 76 | /// Only the last exported component is generated, regardless if this is a Window or not, |
| 77 | /// (and it will be transformed in a Window) |
| 78 | LastExported, |
| 79 | |
| 80 | /// The component with the given name is generated |
| 81 | Named(String), |
| 82 | } |
| 83 | |
| 84 | #[cfg (feature = "software-renderer" )] |
| 85 | pub type FontCache = Rc< |
| 86 | RefCell< |
| 87 | std::collections::HashMap< |
| 88 | i_slint_common::sharedfontdb::fontdb::ID, |
| 89 | fontdue::FontResult<(Arc<fontdue::Font>, Arc<dyn AsRef<[u8]> + Send + Sync>, u32)>, |
| 90 | >, |
| 91 | >, |
| 92 | >; |
| 93 | |
| 94 | /// CompilationConfiguration allows configuring different aspects of the compiler. |
| 95 | #[derive (Clone)] |
| 96 | pub struct CompilerConfiguration { |
| 97 | /// Indicate whether to embed resources such as images in the generated output or whether |
| 98 | /// to retain references to the resources on the file system. |
| 99 | pub embed_resources: EmbedResourcesKind, |
| 100 | /// Whether to use SDF when pre-rendering fonts. |
| 101 | #[cfg (all(feature = "software-renderer" , feature = "sdf-fonts" ))] |
| 102 | pub use_sdf_fonts: bool, |
| 103 | /// The compiler will look in these paths for components used in the file to compile. |
| 104 | pub include_paths: Vec<std::path::PathBuf>, |
| 105 | /// The compiler will look in these paths for library imports. |
| 106 | pub library_paths: HashMap<String, std::path::PathBuf>, |
| 107 | /// the name of the style. (eg: "native") |
| 108 | pub style: Option<String>, |
| 109 | |
| 110 | /// Callback to load import files which is called if the file could not be found |
| 111 | /// |
| 112 | /// The callback should open the file specified by the given file name and |
| 113 | /// return an future that provides the text content of the file as output. |
| 114 | pub open_import_fallback: Option< |
| 115 | Rc<dyn Fn(String) -> Pin<Box<dyn Future<Output = Option<std::io::Result<String>>>>>>, |
| 116 | >, |
| 117 | /// Callback to map URLs for resources |
| 118 | /// |
| 119 | /// The function takes the url and returns the mapped URL (or None if not mapped) |
| 120 | pub resource_url_mapper: |
| 121 | Option<Rc<dyn Fn(&str) -> Pin<Box<dyn Future<Output = Option<String>>>>>>, |
| 122 | |
| 123 | /// Run the pass that inlines all the elements. |
| 124 | /// |
| 125 | /// This may help optimization to optimize the runtime resources usages, |
| 126 | /// but at the cost of much more generated code and binary size. |
| 127 | pub inline_all_elements: bool, |
| 128 | |
| 129 | /// Compile time scale factor to apply to embedded resources such as images and glyphs. |
| 130 | /// If != 1.0 then the scale factor will be set on the `slint::Window`. |
| 131 | pub const_scale_factor: f64, |
| 132 | |
| 133 | /// expose the accessible role and properties |
| 134 | pub accessibility: bool, |
| 135 | |
| 136 | /// Add support for experimental features |
| 137 | pub enable_experimental: bool, |
| 138 | |
| 139 | /// The domain used as one of the parameter to the translate function |
| 140 | pub translation_domain: Option<String>, |
| 141 | /// When Some, this is the path where the translations are looked at to bundle the translations |
| 142 | #[cfg (feature = "bundle-translations" )] |
| 143 | pub translation_path_bundle: Option<std::path::PathBuf>, |
| 144 | |
| 145 | /// Do not generate the hook to create native menus |
| 146 | pub no_native_menu: bool, |
| 147 | |
| 148 | /// C++ namespace |
| 149 | pub cpp_namespace: Option<String>, |
| 150 | |
| 151 | /// Generate debug information for elements (ids, type names) |
| 152 | pub debug_info: bool, |
| 153 | |
| 154 | /// Generate debug hooks to inspect/override properties. |
| 155 | pub debug_hooks: Option<std::hash::RandomState>, |
| 156 | |
| 157 | pub components_to_generate: ComponentSelection, |
| 158 | |
| 159 | #[cfg (feature = "software-renderer" )] |
| 160 | pub font_cache: FontCache, |
| 161 | } |
| 162 | |
| 163 | impl CompilerConfiguration { |
| 164 | pub fn new(output_format: OutputFormat) -> Self { |
| 165 | let embed_resources = if std::env::var_os("SLINT_EMBED_TEXTURES" ).is_some() |
| 166 | || std::env::var_os("DEP_MCU_BOARD_SUPPORT_MCU_EMBED_TEXTURES" ).is_some() |
| 167 | { |
| 168 | #[cfg (not(feature = "software-renderer" ))] |
| 169 | panic!("the software-renderer feature must be enabled in i-slint-compiler when embedding textures" ); |
| 170 | #[cfg (feature = "software-renderer" )] |
| 171 | EmbedResourcesKind::EmbedTextures |
| 172 | } else if let Ok(var) = std::env::var("SLINT_EMBED_RESOURCES" ) { |
| 173 | let var = var.parse::<bool>().unwrap_or_else(|_|{ |
| 174 | panic!("SLINT_EMBED_RESOURCES has incorrect value. Must be either unset, 'true' or 'false'" ) |
| 175 | }); |
| 176 | match var { |
| 177 | true => EmbedResourcesKind::EmbedAllResources, |
| 178 | false => EmbedResourcesKind::OnlyBuiltinResources, |
| 179 | } |
| 180 | } else { |
| 181 | match output_format { |
| 182 | #[cfg (feature = "rust" )] |
| 183 | OutputFormat::Rust => EmbedResourcesKind::EmbedAllResources, |
| 184 | OutputFormat::Interpreter => EmbedResourcesKind::Nothing, |
| 185 | _ => EmbedResourcesKind::OnlyBuiltinResources, |
| 186 | } |
| 187 | }; |
| 188 | |
| 189 | let inline_all_elements = match std::env::var("SLINT_INLINING" ) { |
| 190 | Ok(var) => var.parse::<bool>().unwrap_or_else(|_| { |
| 191 | panic!( |
| 192 | "SLINT_INLINING has incorrect value. Must be either unset, 'true' or 'false'" |
| 193 | ) |
| 194 | }), |
| 195 | // Currently, the interpreter needs the inlining to be on. |
| 196 | Err(_) => output_format == OutputFormat::Interpreter, |
| 197 | }; |
| 198 | |
| 199 | let const_scale_factor = std::env::var("SLINT_SCALE_FACTOR" ) |
| 200 | .ok() |
| 201 | .and_then(|x| x.parse::<f64>().ok()) |
| 202 | .filter(|f| *f > 0.) |
| 203 | .unwrap_or(1.); |
| 204 | |
| 205 | let enable_experimental = std::env::var_os("SLINT_ENABLE_EXPERIMENTAL_FEATURES" ).is_some(); |
| 206 | |
| 207 | let debug_info = std::env::var_os("SLINT_EMIT_DEBUG_INFO" ).is_some(); |
| 208 | |
| 209 | let cpp_namespace = match output_format { |
| 210 | #[cfg (feature = "cpp" )] |
| 211 | OutputFormat::Cpp(config) => match config.namespace { |
| 212 | Some(namespace) => Some(namespace), |
| 213 | None => match std::env::var("SLINT_CPP_NAMESPACE" ) { |
| 214 | Ok(namespace) => Some(namespace), |
| 215 | Err(_) => None, |
| 216 | }, |
| 217 | }, |
| 218 | _ => None, |
| 219 | }; |
| 220 | |
| 221 | Self { |
| 222 | embed_resources, |
| 223 | include_paths: Default::default(), |
| 224 | library_paths: Default::default(), |
| 225 | style: Default::default(), |
| 226 | open_import_fallback: None, |
| 227 | resource_url_mapper: None, |
| 228 | inline_all_elements, |
| 229 | const_scale_factor, |
| 230 | accessibility: true, |
| 231 | enable_experimental, |
| 232 | translation_domain: None, |
| 233 | no_native_menu: false, |
| 234 | cpp_namespace, |
| 235 | debug_info, |
| 236 | debug_hooks: None, |
| 237 | components_to_generate: ComponentSelection::ExportedWindows, |
| 238 | #[cfg (feature = "software-renderer" )] |
| 239 | font_cache: Default::default(), |
| 240 | #[cfg (all(feature = "software-renderer" , feature = "sdf-fonts" ))] |
| 241 | use_sdf_fonts: false, |
| 242 | #[cfg (feature = "bundle-translations" )] |
| 243 | translation_path_bundle: std::env::var("SLINT_BUNDLE_TRANSLATIONS" ) |
| 244 | .ok() |
| 245 | .map(|x| x.into()), |
| 246 | } |
| 247 | } |
| 248 | |
| 249 | #[cfg (feature = "software-renderer" )] |
| 250 | fn load_font_by_id( |
| 251 | &self, |
| 252 | face_id: i_slint_common::sharedfontdb::fontdb::ID, |
| 253 | ) -> fontdue::FontResult<(Arc<fontdue::Font>, Arc<dyn AsRef<[u8]> + Send + Sync>, u32)> { |
| 254 | self.font_cache |
| 255 | .borrow_mut() |
| 256 | .entry(face_id) |
| 257 | .or_insert_with(|| { |
| 258 | i_slint_common::sharedfontdb::FONT_DB.with(|fontdb| { |
| 259 | fontdb |
| 260 | .borrow() |
| 261 | .with_face_data(face_id, |font_data, face_index| { |
| 262 | fontdue::Font::from_bytes( |
| 263 | font_data, |
| 264 | fontdue::FontSettings { |
| 265 | collection_index: face_index, |
| 266 | scale: 40., |
| 267 | ..Default::default() |
| 268 | }, |
| 269 | ) |
| 270 | .map(|fontdue_font| { |
| 271 | ( |
| 272 | Arc::new(fontdue_font), |
| 273 | Arc::new(font_data.to_vec()) |
| 274 | as Arc<dyn AsRef<[u8]> + Send + Sync>, |
| 275 | face_index, |
| 276 | ) |
| 277 | }) |
| 278 | }) |
| 279 | .unwrap_or_else(|| fontdue::FontResult::Err("internal error: corrupt font" )) |
| 280 | }) |
| 281 | }) |
| 282 | .clone() |
| 283 | } |
| 284 | } |
| 285 | |
| 286 | fn prepare_for_compile( |
| 287 | diagnostics: &mut diagnostics::BuildDiagnostics, |
| 288 | #[allow (unused_mut)] mut compiler_config: CompilerConfiguration, |
| 289 | ) -> typeloader::TypeLoader { |
| 290 | #[cfg (feature = "software-renderer" )] |
| 291 | if compiler_config.embed_resources == EmbedResourcesKind::EmbedTextures { |
| 292 | // HACK: disable accessibility when compiling for the software renderer |
| 293 | // accessibility is not supported with backend that support software renderer anyway |
| 294 | compiler_config.accessibility = false; |
| 295 | } |
| 296 | |
| 297 | diagnostics.enable_experimental = compiler_config.enable_experimental; |
| 298 | |
| 299 | let global_type_registry: Rc> = if compiler_config.enable_experimental { |
| 300 | crate::typeregister::TypeRegister::builtin_experimental() |
| 301 | } else { |
| 302 | crate::typeregister::TypeRegister::builtin() |
| 303 | }; |
| 304 | |
| 305 | typeloader::TypeLoader::new(global_type_registry, compiler_config, diag:diagnostics) |
| 306 | } |
| 307 | |
| 308 | pub async fn compile_syntax_node( |
| 309 | doc_node: parser::SyntaxNode, |
| 310 | mut diagnostics: diagnostics::BuildDiagnostics, |
| 311 | #[allow (unused_mut)] mut compiler_config: CompilerConfiguration, |
| 312 | ) -> (object_tree::Document, diagnostics::BuildDiagnostics, typeloader::TypeLoader) { |
| 313 | let mut loader = prepare_for_compile(&mut diagnostics, compiler_config); |
| 314 | |
| 315 | let doc_node: parser::syntax_nodes::Document = doc_node.into(); |
| 316 | |
| 317 | let type_registry = |
| 318 | Rc::new(RefCell::new(typeregister::TypeRegister::new(&loader.global_type_registry))); |
| 319 | let (foreign_imports, reexports) = |
| 320 | loader.load_dependencies_recursively(&doc_node, &mut diagnostics, &type_registry).await; |
| 321 | |
| 322 | let mut doc = crate::object_tree::Document::from_node( |
| 323 | doc_node, |
| 324 | foreign_imports, |
| 325 | reexports, |
| 326 | &mut diagnostics, |
| 327 | &type_registry, |
| 328 | ); |
| 329 | |
| 330 | if !diagnostics.has_errors() { |
| 331 | passes::run_passes(&mut doc, &mut loader, false, &mut diagnostics).await; |
| 332 | } else { |
| 333 | // Don't run all the passes in case of errors because because some invariants are not met. |
| 334 | passes::run_import_passes(&doc, &loader, &mut diagnostics); |
| 335 | } |
| 336 | (doc, diagnostics, loader) |
| 337 | } |
| 338 | |
| 339 | /// Pass a file to the compiler and process it fully, applying all the |
| 340 | /// necessary compilation passes. |
| 341 | /// |
| 342 | /// This returns a `Tuple` containing the actual cleaned `path` to the file, |
| 343 | /// a set of `BuildDiagnostics` and a `TypeLoader` with all compilation passes applied. |
| 344 | pub async fn load_root_file( |
| 345 | path: &Path, |
| 346 | source_path: &Path, |
| 347 | source_code: String, |
| 348 | mut diagnostics: diagnostics::BuildDiagnostics, |
| 349 | #[allow (unused_mut)] mut compiler_config: CompilerConfiguration, |
| 350 | ) -> (std::path::PathBuf, diagnostics::BuildDiagnostics, typeloader::TypeLoader) { |
| 351 | let mut loader: TypeLoader = prepare_for_compile(&mut diagnostics, compiler_config); |
| 352 | |
| 353 | let (path: PathBuf, _) = |
| 354 | loader.load_root_file(path, source_path, source_code, keep_raw:false, &mut diagnostics).await; |
| 355 | |
| 356 | (path, diagnostics, loader) |
| 357 | } |
| 358 | |
| 359 | /// Pass a file to the compiler and process it fully, applying all the |
| 360 | /// necessary compilation passes, just like `load_root_file`. |
| 361 | /// |
| 362 | /// This returns a `Tuple` containing the actual cleaned `path` to the file, |
| 363 | /// a set of `BuildDiagnostics`, a `TypeLoader` with all compilation passes |
| 364 | /// applied and another `TypeLoader` with a minimal set of passes applied to it. |
| 365 | pub async fn load_root_file_with_raw_type_loader( |
| 366 | path: &Path, |
| 367 | source_path: &Path, |
| 368 | source_code: String, |
| 369 | mut diagnostics: diagnostics::BuildDiagnostics, |
| 370 | #[allow (unused_mut)] mut compiler_config: CompilerConfiguration, |
| 371 | ) -> ( |
| 372 | std::path::PathBuf, |
| 373 | diagnostics::BuildDiagnostics, |
| 374 | typeloader::TypeLoader, |
| 375 | Option<typeloader::TypeLoader>, |
| 376 | ) { |
| 377 | let mut loader: TypeLoader = prepare_for_compile(&mut diagnostics, compiler_config); |
| 378 | |
| 379 | let (path: PathBuf, raw_type_loader: Option) = |
| 380 | loader.load_root_file(path, source_path, source_code, keep_raw:true, &mut diagnostics).await; |
| 381 | |
| 382 | (path, diagnostics, loader, raw_type_loader) |
| 383 | } |
| 384 | |