| 1 | //! A [Glyph Substitution Table](https://docs.microsoft.com/en-us/typography/opentype/spec/gsub) |
| 2 | //! implementation. |
| 3 | |
| 4 | // A heavily modified port of https://github.com/harfbuzz/rustybuzz implementation |
| 5 | // originally written by https://github.com/laurmaedje |
| 6 | |
| 7 | use crate::opentype_layout::{ChainedContextLookup, ContextLookup, Coverage, LookupSubtable}; |
| 8 | use crate::parser::{FromSlice, LazyArray16, LazyOffsetArray16, Stream}; |
| 9 | use crate::GlyphId; |
| 10 | |
| 11 | /// A [Single Substitution Subtable](https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#SS). |
| 12 | #[allow (missing_docs)] |
| 13 | #[derive (Clone, Copy, Debug)] |
| 14 | pub enum SingleSubstitution<'a> { |
| 15 | Format1 { |
| 16 | coverage: Coverage<'a>, |
| 17 | delta: i16, |
| 18 | }, |
| 19 | Format2 { |
| 20 | coverage: Coverage<'a>, |
| 21 | substitutes: LazyArray16<'a, GlyphId>, |
| 22 | }, |
| 23 | } |
| 24 | |
| 25 | impl<'a> SingleSubstitution<'a> { |
| 26 | fn parse(data: &'a [u8]) -> Option<Self> { |
| 27 | let mut s = Stream::new(data); |
| 28 | match s.read::<u16>()? { |
| 29 | 1 => { |
| 30 | let coverage = Coverage::parse(s.read_at_offset16(data)?)?; |
| 31 | let delta = s.read::<i16>()?; |
| 32 | Some(Self::Format1 { coverage, delta }) |
| 33 | } |
| 34 | 2 => { |
| 35 | let coverage = Coverage::parse(s.read_at_offset16(data)?)?; |
| 36 | let count = s.read::<u16>()?; |
| 37 | let substitutes = s.read_array16(count)?; |
| 38 | Some(Self::Format2 { |
| 39 | coverage, |
| 40 | substitutes, |
| 41 | }) |
| 42 | } |
| 43 | _ => None, |
| 44 | } |
| 45 | } |
| 46 | |
| 47 | /// Returns the subtable coverage. |
| 48 | #[inline ] |
| 49 | pub fn coverage(&self) -> Coverage<'a> { |
| 50 | match self { |
| 51 | Self::Format1 { coverage, .. } => *coverage, |
| 52 | Self::Format2 { coverage, .. } => *coverage, |
| 53 | } |
| 54 | } |
| 55 | } |
| 56 | |
| 57 | /// A sequence of glyphs for |
| 58 | /// [Multiple Substitution Subtable](https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#MS). |
| 59 | #[derive (Clone, Copy, Debug)] |
| 60 | pub struct Sequence<'a> { |
| 61 | /// A list of substitute glyphs. |
| 62 | pub substitutes: LazyArray16<'a, GlyphId>, |
| 63 | } |
| 64 | |
| 65 | impl<'a> FromSlice<'a> for Sequence<'a> { |
| 66 | fn parse(data: &'a [u8]) -> Option<Self> { |
| 67 | let mut s: Stream<'_> = Stream::new(data); |
| 68 | let count: u16 = s.read::<u16>()?; |
| 69 | let substitutes: LazyArray16<'_, GlyphId> = s.read_array16(count)?; |
| 70 | Some(Self { substitutes }) |
| 71 | } |
| 72 | } |
| 73 | |
| 74 | /// A list of [`Sequence`] tables. |
| 75 | pub type SequenceList<'a> = LazyOffsetArray16<'a, Sequence<'a>>; |
| 76 | |
| 77 | /// A [Multiple Substitution Subtable](https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#MS). |
| 78 | #[allow (missing_docs)] |
| 79 | #[derive (Clone, Copy, Debug)] |
| 80 | pub struct MultipleSubstitution<'a> { |
| 81 | pub coverage: Coverage<'a>, |
| 82 | pub sequences: SequenceList<'a>, |
| 83 | } |
| 84 | |
| 85 | impl<'a> MultipleSubstitution<'a> { |
| 86 | fn parse(data: &'a [u8]) -> Option<Self> { |
| 87 | let mut s: Stream<'_> = Stream::new(data); |
| 88 | match s.read::<u16>()? { |
| 89 | 1 => { |
| 90 | let coverage: Coverage<'_> = Coverage::parse(data:s.read_at_offset16(data)?)?; |
| 91 | let count: u16 = s.read::<u16>()?; |
| 92 | let offsets: LazyArray16<'_, Option> = s.read_array16(count)?; |
| 93 | Some(Self { |
| 94 | coverage, |
| 95 | sequences: SequenceList::new(data, offsets), |
| 96 | }) |
| 97 | } |
| 98 | _ => None, |
| 99 | } |
| 100 | } |
| 101 | } |
| 102 | |
| 103 | /// A list of glyphs for |
| 104 | /// [Alternate Substitution Subtable](https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#AS). |
| 105 | #[derive (Clone, Copy, Debug)] |
| 106 | pub struct AlternateSet<'a> { |
| 107 | /// Array of alternate glyph IDs, in arbitrary order. |
| 108 | pub alternates: LazyArray16<'a, GlyphId>, |
| 109 | } |
| 110 | |
| 111 | impl<'a> FromSlice<'a> for AlternateSet<'a> { |
| 112 | fn parse(data: &'a [u8]) -> Option<Self> { |
| 113 | let mut s: Stream<'_> = Stream::new(data); |
| 114 | let count: u16 = s.read::<u16>()?; |
| 115 | let alternates: LazyArray16<'_, GlyphId> = s.read_array16(count)?; |
| 116 | Some(Self { alternates }) |
| 117 | } |
| 118 | } |
| 119 | |
| 120 | /// A set of [`AlternateSet`]. |
| 121 | pub type AlternateSets<'a> = LazyOffsetArray16<'a, AlternateSet<'a>>; |
| 122 | |
| 123 | /// A [Alternate Substitution Subtable](https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#AS). |
| 124 | #[allow (missing_docs)] |
| 125 | #[derive (Clone, Copy, Debug)] |
| 126 | pub struct AlternateSubstitution<'a> { |
| 127 | pub coverage: Coverage<'a>, |
| 128 | pub alternate_sets: AlternateSets<'a>, |
| 129 | } |
| 130 | |
| 131 | impl<'a> AlternateSubstitution<'a> { |
| 132 | fn parse(data: &'a [u8]) -> Option<Self> { |
| 133 | let mut s: Stream<'_> = Stream::new(data); |
| 134 | match s.read::<u16>()? { |
| 135 | 1 => { |
| 136 | let coverage: Coverage<'_> = Coverage::parse(data:s.read_at_offset16(data)?)?; |
| 137 | let count: u16 = s.read::<u16>()?; |
| 138 | let offsets: LazyArray16<'_, Option> = s.read_array16(count)?; |
| 139 | Some(Self { |
| 140 | coverage, |
| 141 | alternate_sets: AlternateSets::new(data, offsets), |
| 142 | }) |
| 143 | } |
| 144 | _ => None, |
| 145 | } |
| 146 | } |
| 147 | } |
| 148 | |
| 149 | /// Glyph components for one ligature. |
| 150 | #[derive (Clone, Copy, Debug)] |
| 151 | pub struct Ligature<'a> { |
| 152 | /// Ligature to substitute. |
| 153 | pub glyph: GlyphId, |
| 154 | /// Glyph components for one ligature. |
| 155 | pub components: LazyArray16<'a, GlyphId>, |
| 156 | } |
| 157 | |
| 158 | impl<'a> FromSlice<'a> for Ligature<'a> { |
| 159 | fn parse(data: &'a [u8]) -> Option<Self> { |
| 160 | let mut s: Stream<'_> = Stream::new(data); |
| 161 | let glyph: GlyphId = s.read::<GlyphId>()?; |
| 162 | let count: u16 = s.read::<u16>()?; |
| 163 | let components: LazyArray16<'_, GlyphId> = s.read_array16(count.checked_sub(1)?)?; |
| 164 | Some(Self { glyph, components }) |
| 165 | } |
| 166 | } |
| 167 | |
| 168 | /// A [`Ligature`] set. |
| 169 | pub type LigatureSet<'a> = LazyOffsetArray16<'a, Ligature<'a>>; |
| 170 | |
| 171 | impl<'a> FromSlice<'a> for LigatureSet<'a> { |
| 172 | fn parse(data: &'a [u8]) -> Option<Self> { |
| 173 | Self::parse(data) |
| 174 | } |
| 175 | } |
| 176 | |
| 177 | /// A list of [`Ligature`] sets. |
| 178 | pub type LigatureSets<'a> = LazyOffsetArray16<'a, LigatureSet<'a>>; |
| 179 | |
| 180 | /// A [Ligature Substitution Subtable](https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#LS). |
| 181 | #[allow (missing_docs)] |
| 182 | #[derive (Clone, Copy, Debug)] |
| 183 | pub struct LigatureSubstitution<'a> { |
| 184 | pub coverage: Coverage<'a>, |
| 185 | pub ligature_sets: LigatureSets<'a>, |
| 186 | } |
| 187 | |
| 188 | impl<'a> LigatureSubstitution<'a> { |
| 189 | fn parse(data: &'a [u8]) -> Option<Self> { |
| 190 | let mut s: Stream<'_> = Stream::new(data); |
| 191 | match s.read::<u16>()? { |
| 192 | 1 => { |
| 193 | let coverage: Coverage<'_> = Coverage::parse(data:s.read_at_offset16(data)?)?; |
| 194 | let count: u16 = s.read::<u16>()?; |
| 195 | let offsets: LazyArray16<'_, Option> = s.read_array16(count)?; |
| 196 | Some(Self { |
| 197 | coverage, |
| 198 | ligature_sets: LigatureSets::new(data, offsets), |
| 199 | }) |
| 200 | } |
| 201 | _ => None, |
| 202 | } |
| 203 | } |
| 204 | } |
| 205 | |
| 206 | /// A [Reverse Chaining Contextual Single Substitution Subtable]( |
| 207 | /// https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#RCCS). |
| 208 | #[allow (missing_docs)] |
| 209 | #[derive (Clone, Copy, Debug)] |
| 210 | pub struct ReverseChainSingleSubstitution<'a> { |
| 211 | pub coverage: Coverage<'a>, |
| 212 | pub backtrack_coverages: LazyOffsetArray16<'a, Coverage<'a>>, |
| 213 | pub lookahead_coverages: LazyOffsetArray16<'a, Coverage<'a>>, |
| 214 | pub substitutes: LazyArray16<'a, GlyphId>, |
| 215 | } |
| 216 | |
| 217 | impl<'a> ReverseChainSingleSubstitution<'a> { |
| 218 | fn parse(data: &'a [u8]) -> Option<Self> { |
| 219 | let mut s: Stream<'_> = Stream::new(data); |
| 220 | match s.read::<u16>()? { |
| 221 | 1 => { |
| 222 | let coverage: Coverage<'_> = Coverage::parse(data:s.read_at_offset16(data)?)?; |
| 223 | let backtrack_count: u16 = s.read::<u16>()?; |
| 224 | let backtrack_coverages: LazyArray16<'_, Option> = s.read_array16(backtrack_count)?; |
| 225 | let lookahead_count: u16 = s.read::<u16>()?; |
| 226 | let lookahead_coverages: LazyArray16<'_, Option> = s.read_array16(lookahead_count)?; |
| 227 | let substitute_count: u16 = s.read::<u16>()?; |
| 228 | let substitutes: LazyArray16<'_, GlyphId> = s.read_array16(substitute_count)?; |
| 229 | Some(Self { |
| 230 | coverage, |
| 231 | backtrack_coverages: LazyOffsetArray16::new(data, offsets:backtrack_coverages), |
| 232 | lookahead_coverages: LazyOffsetArray16::new(data, offsets:lookahead_coverages), |
| 233 | substitutes, |
| 234 | }) |
| 235 | } |
| 236 | _ => None, |
| 237 | } |
| 238 | } |
| 239 | } |
| 240 | |
| 241 | /// A glyph substitution |
| 242 | /// [lookup subtable](https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#table-organization) |
| 243 | /// enumeration. |
| 244 | #[allow (missing_docs)] |
| 245 | #[derive (Clone, Copy, Debug)] |
| 246 | pub enum SubstitutionSubtable<'a> { |
| 247 | Single(SingleSubstitution<'a>), |
| 248 | Multiple(MultipleSubstitution<'a>), |
| 249 | Alternate(AlternateSubstitution<'a>), |
| 250 | Ligature(LigatureSubstitution<'a>), |
| 251 | Context(ContextLookup<'a>), |
| 252 | ChainContext(ChainedContextLookup<'a>), |
| 253 | ReverseChainSingle(ReverseChainSingleSubstitution<'a>), |
| 254 | } |
| 255 | |
| 256 | impl<'a> LookupSubtable<'a> for SubstitutionSubtable<'a> { |
| 257 | fn parse(data: &'a [u8], kind: u16) -> Option<Self> { |
| 258 | match kind { |
| 259 | 1 => SingleSubstitution::parse(data).map(Self::Single), |
| 260 | 2 => MultipleSubstitution::parse(data).map(Self::Multiple), |
| 261 | 3 => AlternateSubstitution::parse(data).map(Self::Alternate), |
| 262 | 4 => LigatureSubstitution::parse(data).map(Self::Ligature), |
| 263 | 5 => ContextLookup::parse(data).map(Self::Context), |
| 264 | 6 => ChainedContextLookup::parse(data).map(Self::ChainContext), |
| 265 | 7 => crate::ggg::parse_extension_lookup(data, Self::parse), |
| 266 | 8 => ReverseChainSingleSubstitution::parse(data).map(Self::ReverseChainSingle), |
| 267 | _ => None, |
| 268 | } |
| 269 | } |
| 270 | } |
| 271 | |
| 272 | impl<'a> SubstitutionSubtable<'a> { |
| 273 | /// Returns the subtable coverage. |
| 274 | #[inline ] |
| 275 | pub fn coverage(&self) -> Coverage<'a> { |
| 276 | match self { |
| 277 | Self::Single(t: &SingleSubstitution<'a>) => t.coverage(), |
| 278 | Self::Multiple(t: &MultipleSubstitution<'a>) => t.coverage, |
| 279 | Self::Alternate(t: &AlternateSubstitution<'a>) => t.coverage, |
| 280 | Self::Ligature(t: &LigatureSubstitution<'a>) => t.coverage, |
| 281 | Self::Context(t: &ContextLookup<'a>) => t.coverage(), |
| 282 | Self::ChainContext(t: &ChainedContextLookup<'a>) => t.coverage(), |
| 283 | Self::ReverseChainSingle(t: &ReverseChainSingleSubstitution<'a>) => t.coverage, |
| 284 | } |
| 285 | } |
| 286 | |
| 287 | /// Checks that the current subtable is *Reverse Chaining Contextual Single*. |
| 288 | #[inline ] |
| 289 | pub fn is_reverse(&self) -> bool { |
| 290 | matches!(self, Self::ReverseChainSingle(_)) |
| 291 | } |
| 292 | } |
| 293 | |