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 };