lib/filigree/src/shape/model.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const font = @import("../font/root.zig");
  3 const unicode_data = @import("unicode");
  4 
  5 pub const FeatureRange = font.FeatureRange;
  6 pub const FeatureSetting = font.FeatureSetting;
  7 pub const VariationSetting = font.VariationSetting;
  8 
  9 pub const Direction = enum {
 10     ltr,
 11     rtl,
 12 };
 13 
 14 pub const WritingMode = enum {
 15     horizontal,
 16     vertical,
 17 };
 18 
 19 pub const OutputOrder = enum {
 20     visual,
 21     logical,
 22 };
 23 
 24 pub const ClusterMode = enum {
 25     monotone_graphemes,
 26     monotone_characters,
 27     characters,
 28     graphemes,
 29 };
 30 
 31 pub const DefaultIgnorablePolicy = enum {
 32     hide,
 33     preserve,
 34     remove,
 35 };
 36 
 37 pub const MissingGlyphPolicy = enum {
 38     preserve,
 39     fail,
 40 };
 41 
 42 pub const DottedCirclePolicy = enum {
 43     disabled,
 44     insert,
 45 };
 46 
 47 pub const Source = unicode_data.Source;
 48 pub const SourceRange = unicode_data.SourceRange;
 49 
 50 pub const GlyphSpan = struct {
 51     start: u32,
 52     end: u32,
 53 };
 54 
 55 pub const GlyphFlags = packed struct(u8) {
 56     unsafe_to_break: bool = false,
 57     unsafe_to_concat: bool = false,
 58     safe_to_insert_tatweel: bool = false,
 59     missing_glyph: bool = false,
 60     default_ignorable: bool = false,
 61     synthetic: bool = false,
 62     reserved: u2 = 0,
 63 };
 64 
 65 pub const GlyphClass = font.GlyphClass;
 66 
 67 pub const GlyphAttachmentKind = enum {
 68     base,
 69     ligature,
 70     mark,
 71 };
 72 
 73 pub const GlyphAttachment = struct {
 74     kind: GlyphAttachmentKind,
 75     target_glyph_index: u32,
 76     ligature_component: u16 = 0,
 77 };
 78 
 79 pub const LigatureCaret = struct {
 80     x_offset: i32,
 81     synthesized: bool = false,
 82 };
 83 
 84 pub const ShapedGlyph = struct {
 85     glyph_id: u32,
 86     cluster: u32,
 87     cluster_index: u32 = 0,
 88     source_start: u32 = 0,
 89     source_end: u32 = 0,
 90     source_codepoint_count: u16 = 1,
 91     x_advance: i32,
 92     y_advance: i32,
 93     x_offset: i32,
 94     y_offset: i32,
 95     flags: GlyphFlags = .{},
 96     glyph_class: GlyphClass = .unknown,
 97     attachment: ?GlyphAttachment = null,
 98     ligature_caret_start: u32 = 0,
 99     ligature_caret_count: u16 = 0,
100 };
101 
102 pub const Cluster = struct {
103     source: SourceRange,
104     glyphs: GlyphSpan,
105     flags: GlyphFlags = .{},
106     codepoint_count: u16 = 1,
107 };
108 
109 pub const GlyphRun = struct {
110     glyphs: []const ShapedGlyph,
111     clusters: []const Cluster,
112     ligature_carets: []const LigatureCaret,
113     total_x_advance: i32,
114     total_y_advance: i32,
115     direction: Direction,
116     writing_mode: WritingMode,
117     output_order: OutputOrder,
118 
119     pub fn clusterMap(self: GlyphRun) ClusterMap {
120         return .{ .run = self };
121     }
122 };
123 
124 pub const ClusterMap = struct {
125     run: GlyphRun,
126 
127     pub fn byteOffsetToCluster(self: ClusterMap, byte_offset: usize) ?usize {
128         if (byte_offset > std.math.maxInt(u32)) return null;
129         const offset: u32 = @intCast(byte_offset);
130         for (self.run.clusters, 0..) |cluster, i| {
131             if (offset >= cluster.source.start and offset < cluster.source.end) return i;
132         }
133         return null;
134     }
135 
136     pub fn clusterSourceRange(self: ClusterMap, cluster_index: usize) ?SourceRange {
137         if (cluster_index >= self.run.clusters.len) return null;
138         return self.run.clusters[cluster_index].source;
139     }
140 
141     pub fn clusterGlyphSpan(self: ClusterMap, cluster_index: usize) ?GlyphSpan {
142         if (cluster_index >= self.run.clusters.len) return null;
143         return self.run.clusters[cluster_index].glyphs;
144     }
145 
146     pub fn glyphToCluster(self: ClusterMap, glyph_index: usize) ?usize {
147         if (glyph_index >= self.run.glyphs.len) return null;
148         return self.run.glyphs[glyph_index].cluster_index;
149     }
150 
151     pub fn clusterCaretStopCount(self: ClusterMap, cluster_index: usize) ?usize {
152         if (cluster_index >= self.run.clusters.len) return null;
153         return 2 + (self.clusterLigatureCaretCount(cluster_index) orelse return null);
154     }
155 
156     pub fn clusterCaretStop(self: ClusterMap, cluster_index: usize, caret_index: usize) ?u32 {
157         if (cluster_index >= self.run.clusters.len) return null;
158         const stop_count = self.clusterCaretStopCount(cluster_index) orelse return null;
159         if (caret_index >= stop_count) return null;
160         const source = self.run.clusters[cluster_index].source;
161         if (caret_index == 0) return source.start;
162         if (caret_index + 1 == stop_count) return source.end;
163         const source_len = source.end - source.start;
164         const interior_count = stop_count - 2;
165         if (source_len == 0 or interior_count == 0) return null;
166         const relative = (@as(u64, caret_index) * @as(u64, source_len)) / @as(u64, interior_count + 1);
167         return source.start + @as(u32, @intCast(relative));
168     }
169 
170     pub fn clusterLigatureCaretCount(self: ClusterMap, cluster_index: usize) ?usize {
171         if (cluster_index >= self.run.clusters.len) return null;
172         const glyph = self.clusterSingleLigatureGlyph(cluster_index) orelse return 0;
173         return glyph.ligature_caret_count;
174     }
175 
176     pub fn clusterLigatureCaret(self: ClusterMap, cluster_index: usize, caret_index: usize) ?LigatureCaret {
177         if (cluster_index >= self.run.clusters.len) return null;
178         const glyph = self.clusterSingleLigatureGlyph(cluster_index) orelse return null;
179         if (caret_index >= glyph.ligature_caret_count) return null;
180         return self.run.ligature_carets[@as(usize, @intCast(glyph.ligature_caret_start)) + caret_index];
181     }
182 
183     pub fn selectableAsUnitOnly(self: ClusterMap, cluster_index: usize) ?bool {
184         if (cluster_index >= self.run.clusters.len) return null;
185         const cluster = self.run.clusters[cluster_index];
186         return cluster.codepoint_count > 1 or cluster.glyphs.end - cluster.glyphs.start > 1;
187     }
188 
189     pub fn breakRequiresReshaping(self: ClusterMap, cluster_index: usize) ?bool {
190         if (cluster_index >= self.run.clusters.len) return null;
191         return self.run.clusters[cluster_index].flags.unsafe_to_break;
192     }
193 
194     fn clusterSingleLigatureGlyph(self: ClusterMap, cluster_index: usize) ?ShapedGlyph {
195         const cluster = self.run.clusters[cluster_index];
196         if (cluster.glyphs.end <= cluster.glyphs.start) return null;
197         if (cluster.glyphs.end - cluster.glyphs.start != 1) return null;
198         const glyph_index: usize = @intCast(cluster.glyphs.start);
199         if (glyph_index >= self.run.glyphs.len) return null;
200         const glyph = self.run.glyphs[glyph_index];
201         if (glyph.glyph_class != .ligature or glyph.ligature_caret_count == 0) return null;
202         const caret_start: usize = @intCast(glyph.ligature_caret_start);
203         const caret_count: usize = glyph.ligature_caret_count;
204         if (caret_start > self.run.ligature_carets.len) return null;
205         if (caret_count > self.run.ligature_carets.len - caret_start) return null;
206         return glyph;
207     }
208 };