|
1 | 1 | use crate::hb::{
|
2 |
| - hb_font_t, |
| 2 | + common::TagExt, |
| 3 | + hb_font_t, hb_tag_t, |
3 | 4 | ot_layout::LayoutLookup,
|
4 | 5 | ot_layout_gsubgpos::{Apply, WouldApply, WouldApplyContext, OT::hb_ot_apply_context_t},
|
5 | 6 | set_digest::hb_set_digest_ext,
|
6 | 7 | };
|
7 | 8 | use skrifa::raw::{
|
8 | 9 | tables::{
|
9 | 10 | gdef::Gdef,
|
10 |
| - layout::{ClassDef, CoverageTable}, |
| 11 | + gpos::Gpos, |
| 12 | + gsub::{FeatureList, FeatureVariations, Gsub, ScriptList}, |
| 13 | + layout::{ClassDef, Condition, CoverageTable, Feature, LangSys, Script}, |
11 | 14 | variations::ItemVariationStore,
|
12 | 15 | },
|
| 16 | + types::{F2Dot14, Scalar}, |
13 | 17 | ReadError, TableProvider,
|
14 | 18 | };
|
15 | 19 | use ttf_parser::GlyphId;
|
@@ -159,6 +163,259 @@ impl LookupInfo {
|
159 | 163 | }
|
160 | 164 | }
|
161 | 165 |
|
| 166 | +pub enum LayoutTable<'a> { |
| 167 | + Gsub(Gsub<'a>), |
| 168 | + Gpos(Gpos<'a>), |
| 169 | +} |
| 170 | + |
| 171 | +fn conv_tag(tag: hb_tag_t) -> skrifa::raw::types::Tag { |
| 172 | + skrifa::raw::types::Tag::from_u32(tag.0) |
| 173 | +} |
| 174 | + |
| 175 | +impl<'a> LayoutTable<'a> { |
| 176 | + fn script_list(&self) -> Option<ScriptList<'a>> { |
| 177 | + match self { |
| 178 | + Self::Gsub(gsub) => gsub.script_list().ok(), |
| 179 | + Self::Gpos(gpos) => gpos.script_list().ok(), |
| 180 | + } |
| 181 | + } |
| 182 | + |
| 183 | + fn feature_list(&self) -> Option<FeatureList<'a>> { |
| 184 | + match self { |
| 185 | + Self::Gsub(gsub) => gsub.feature_list().ok(), |
| 186 | + Self::Gpos(gpos) => gpos.feature_list().ok(), |
| 187 | + } |
| 188 | + } |
| 189 | + |
| 190 | + fn feature_variations(&self) -> Option<FeatureVariations<'a>> { |
| 191 | + match self { |
| 192 | + Self::Gsub(gsub) => gsub.feature_variations(), |
| 193 | + Self::Gpos(gpos) => gpos.feature_variations(), |
| 194 | + } |
| 195 | + .transpose() |
| 196 | + .ok() |
| 197 | + .flatten() |
| 198 | + } |
| 199 | + |
| 200 | + fn script_index(&self, tag: hb_tag_t) -> Option<u16> { |
| 201 | + let list = self.script_list()?; |
| 202 | + let tag = conv_tag(tag); |
| 203 | + list.script_records() |
| 204 | + .binary_search_by_key(&tag, |rec| rec.script_tag()) |
| 205 | + .map(|index| index as u16) |
| 206 | + .ok() |
| 207 | + } |
| 208 | + |
| 209 | + fn script(&self, index: u16) -> Option<Script<'a>> { |
| 210 | + let list = self.script_list()?; |
| 211 | + let record = list.script_records().get(index as usize)?; |
| 212 | + record.script(list.offset_data()).ok() |
| 213 | + } |
| 214 | + |
| 215 | + fn langsys_index(&self, script_index: u16, tag: hb_tag_t) -> Option<u16> { |
| 216 | + let script = self.script(script_index)?; |
| 217 | + let tag = conv_tag(tag); |
| 218 | + script |
| 219 | + .lang_sys_records() |
| 220 | + .binary_search_by_key(&tag, |rec| rec.lang_sys_tag()) |
| 221 | + .map(|index| index as u16) |
| 222 | + .ok() |
| 223 | + } |
| 224 | + |
| 225 | + fn langsys(&self, script_index: u16, langsys_index: Option<u16>) -> Option<LangSys<'a>> { |
| 226 | + let script = self.script(script_index)?; |
| 227 | + if let Some(index) = langsys_index { |
| 228 | + let record = script.lang_sys_records().get(index as usize)?; |
| 229 | + record.lang_sys(script.offset_data()).ok() |
| 230 | + } else { |
| 231 | + script.default_lang_sys().transpose().ok().flatten() |
| 232 | + } |
| 233 | + } |
| 234 | + |
| 235 | + pub(crate) fn feature(&self, index: u16) -> Option<Feature<'a>> { |
| 236 | + let list = self.feature_list()?; |
| 237 | + let record = list.feature_records().get(index as usize)?; |
| 238 | + record.feature(list.offset_data()).ok() |
| 239 | + } |
| 240 | + |
| 241 | + fn feature_tag(&self, index: u16) -> Option<hb_tag_t> { |
| 242 | + let list = self.feature_list()?; |
| 243 | + let record = list.feature_records().get(index as usize)?; |
| 244 | + Some(hb_tag_t(u32::from_be_bytes(record.feature_tag().to_raw()))) |
| 245 | + } |
| 246 | + |
| 247 | + pub(crate) fn feature_variation_index(&self, coords: &[F2Dot14]) -> Option<u32> { |
| 248 | + let feature_variations = self.feature_variations()?; |
| 249 | + for (index, rec) in feature_variations |
| 250 | + .feature_variation_records() |
| 251 | + .iter() |
| 252 | + .enumerate() |
| 253 | + { |
| 254 | + // If the ConditionSet offset is 0, this is treated as the |
| 255 | + // universal condition: all contexts are matched. |
| 256 | + if rec.condition_set_offset().is_null() { |
| 257 | + return Some(index as u32); |
| 258 | + } |
| 259 | + let Some(Ok(condition_set)) = rec.condition_set(feature_variations.offset_data()) |
| 260 | + else { |
| 261 | + continue; |
| 262 | + }; |
| 263 | + // Otherwise, all conditions must be satisfied. |
| 264 | + if condition_set |
| 265 | + .conditions() |
| 266 | + .iter() |
| 267 | + // .. except we ignore errors |
| 268 | + .filter_map(|cond| cond.ok()) |
| 269 | + .all(|cond| match cond { |
| 270 | + Condition::Format1AxisRange(format1) => { |
| 271 | + let coord = coords |
| 272 | + .get(format1.axis_index() as usize) |
| 273 | + .copied() |
| 274 | + .unwrap_or_default(); |
| 275 | + coord >= format1.filter_range_min_value() |
| 276 | + && coord <= format1.filter_range_max_value() |
| 277 | + } |
| 278 | + _ => false, |
| 279 | + }) |
| 280 | + { |
| 281 | + return Some(index as u32); |
| 282 | + } |
| 283 | + } |
| 284 | + None |
| 285 | + } |
| 286 | + |
| 287 | + pub(crate) fn feature_substitution( |
| 288 | + &self, |
| 289 | + variation_index: u32, |
| 290 | + feature_index: u16, |
| 291 | + ) -> Option<Feature<'a>> { |
| 292 | + let feature_variations = self.feature_variations()?; |
| 293 | + let record = feature_variations |
| 294 | + .feature_variation_records() |
| 295 | + .get(variation_index as usize)?; |
| 296 | + let subst_table = record |
| 297 | + .feature_table_substitution(feature_variations.offset_data())? |
| 298 | + .ok()?; |
| 299 | + let subst_records = subst_table.substitutions(); |
| 300 | + match subst_records.binary_search_by_key(&feature_index, |subst| subst.feature_index()) { |
| 301 | + Ok(ix) => Some( |
| 302 | + subst_records |
| 303 | + .get(ix)? |
| 304 | + .alternate_feature(subst_table.offset_data()) |
| 305 | + .ok()?, |
| 306 | + ), |
| 307 | + _ => None, |
| 308 | + } |
| 309 | + } |
| 310 | + |
| 311 | + pub(crate) fn feature_index(&self, tag: hb_tag_t) -> Option<u16> { |
| 312 | + let list = self.feature_list()?; |
| 313 | + let tag = conv_tag(tag); |
| 314 | + for (index, feature) in list.feature_records().iter().enumerate() { |
| 315 | + if feature.feature_tag() == tag { |
| 316 | + return Some(index as u16); |
| 317 | + } |
| 318 | + } |
| 319 | + None |
| 320 | + } |
| 321 | + |
| 322 | + pub(crate) fn lookup_count(&self) -> u16 { |
| 323 | + match self { |
| 324 | + Self::Gsub(gsub) => gsub |
| 325 | + .lookup_list() |
| 326 | + .map(|list| list.lookup_count()) |
| 327 | + .unwrap_or_default(), |
| 328 | + Self::Gpos(gpos) => gpos |
| 329 | + .lookup_list() |
| 330 | + .map(|list| list.lookup_count()) |
| 331 | + .unwrap_or_default(), |
| 332 | + } |
| 333 | + } |
| 334 | +} |
| 335 | + |
| 336 | +impl crate::hb::ot_layout::LayoutTableExt for LayoutTable<'_> { |
| 337 | + // hb_ot_layout_table_select_script |
| 338 | + /// Returns true + index and tag of the first found script tag in the given GSUB or GPOS table |
| 339 | + /// or false + index and tag if falling back to a default script. |
| 340 | + fn select_script(&self, script_tags: &[hb_tag_t]) -> Option<(bool, u16, hb_tag_t)> { |
| 341 | + for &tag in script_tags { |
| 342 | + if let Some(index) = self.script_index(tag) { |
| 343 | + return Some((true, index, tag)); |
| 344 | + } |
| 345 | + } |
| 346 | + |
| 347 | + for &tag in &[ |
| 348 | + // try finding 'DFLT' |
| 349 | + hb_tag_t::default_script(), |
| 350 | + // try with 'dflt'; MS site has had typos and many fonts use it now :( |
| 351 | + hb_tag_t::default_language(), |
| 352 | + // try with 'latn'; some old fonts put their features there even though |
| 353 | + // they're really trying to support Thai, for example :( |
| 354 | + hb_tag_t::from_bytes(b"latn"), |
| 355 | + ] { |
| 356 | + if let Some(index) = self.script_index(tag) { |
| 357 | + return Some((false, index, tag)); |
| 358 | + } |
| 359 | + } |
| 360 | + |
| 361 | + None |
| 362 | + } |
| 363 | + |
| 364 | + // hb_ot_layout_script_select_language |
| 365 | + /// Returns the index of the first found language tag in the given GSUB or GPOS table, |
| 366 | + /// underneath the specified script index. |
| 367 | + fn select_script_language(&self, script_index: u16, lang_tags: &[hb_tag_t]) -> Option<u16> { |
| 368 | + for &tag in lang_tags { |
| 369 | + if let Some(index) = self.langsys_index(script_index, tag) { |
| 370 | + return Some(index); |
| 371 | + } |
| 372 | + } |
| 373 | + |
| 374 | + // try finding 'dflt' |
| 375 | + if let Some(index) = self.langsys_index(script_index, hb_tag_t::default_language()) { |
| 376 | + return Some(index); |
| 377 | + } |
| 378 | + |
| 379 | + None |
| 380 | + } |
| 381 | + |
| 382 | + // hb_ot_layout_language_get_required_feature |
| 383 | + /// Returns the index and tag of a required feature in the given GSUB or GPOS table, |
| 384 | + /// underneath the specified script and language. |
| 385 | + fn get_required_language_feature( |
| 386 | + &self, |
| 387 | + script_index: u16, |
| 388 | + lang_index: Option<u16>, |
| 389 | + ) -> Option<(u16, hb_tag_t)> { |
| 390 | + let sys = self.langsys(script_index, lang_index)?; |
| 391 | + let idx = sys.required_feature_index(); |
| 392 | + if idx == 0xFFFF { |
| 393 | + return None; |
| 394 | + } |
| 395 | + let tag = self.feature_tag(idx)?; |
| 396 | + Some((idx, tag)) |
| 397 | + } |
| 398 | + |
| 399 | + // hb_ot_layout_language_find_feature |
| 400 | + /// Returns the index of a given feature tag in the given GSUB or GPOS table, |
| 401 | + /// underneath the specified script and language. |
| 402 | + fn find_language_feature( |
| 403 | + &self, |
| 404 | + script_index: u16, |
| 405 | + lang_index: Option<u16>, |
| 406 | + feature_tag: hb_tag_t, |
| 407 | + ) -> Option<u16> { |
| 408 | + let sys = self.langsys(script_index, lang_index)?; |
| 409 | + for index in sys.feature_indices() { |
| 410 | + let index = index.get(); |
| 411 | + if self.feature_tag(index) == Some(feature_tag) { |
| 412 | + return Some(index); |
| 413 | + } |
| 414 | + } |
| 415 | + None |
| 416 | + } |
| 417 | +} |
| 418 | + |
162 | 419 | fn coverage_index(coverage: Result<CoverageTable, ReadError>, gid: GlyphId) -> Option<u16> {
|
163 | 420 | let gid = skrifa::GlyphId16::new(gid.0);
|
164 | 421 | coverage.ok().and_then(|coverage| coverage.get(gid))
|
|
0 commit comments