ea 0.6.106 → 0.6.108
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/config/themes/119.yml +3 -2
- data/lib/ea/fonts/cambria_7_styled_widths.json +293 -0
- data/lib/ea/fonts/metrics.rb +50 -7
- data/lib/ea/model/diagram_element.rb +4 -0
- data/lib/ea/sources/qea/diagram_builder/auto_routing.rb +124 -11
- data/lib/ea/sources/qea/diagram_builder/font_settings.rb +50 -0
- data/lib/ea/sources/qea/diagram_builder/routing.rb +82 -82
- data/lib/ea/sources/qea/diagram_builder.rb +9 -30
- data/lib/ea/sources/qea/instance_builder.rb +1 -0
- data/lib/ea/sources/qea/property_builder.rb +11 -1
- data/lib/ea/svg/ea_emitter/color_resolver.rb +1 -2
- data/lib/ea/svg/ea_emitter/compartment/attributes.rb +2 -1
- data/lib/ea/svg/ea_emitter/compartment/constraints.rb +5 -14
- data/lib/ea/svg/ea_emitter/compartment/enum_literals.rb +11 -5
- data/lib/ea/svg/ea_emitter/compartment/header.rb +2 -1
- data/lib/ea/svg/ea_emitter/compartment/note_body.rb +6 -4
- data/lib/ea/svg/ea_emitter/compartment/note_html.rb +66 -0
- data/lib/ea/svg/ea_emitter/compartment/operations.rb +2 -1
- data/lib/ea/svg/ea_emitter/compartment.rb +1 -0
- data/lib/ea/svg/ea_emitter/connector_redock.rb +30 -14
- data/lib/ea/svg/ea_emitter/element/attribute_line_builder.rb +11 -9
- data/lib/ea/svg/ea_emitter/element/attribute_renderer.rb +42 -17
- data/lib/ea/svg/ea_emitter/element/compartment_geometry.rb +46 -15
- data/lib/ea/svg/ea_emitter/element/constraint_renderer.rb +34 -2
- data/lib/ea/svg/ea_emitter/element/enumeration_literal_renderer.rb +8 -5
- data/lib/ea/svg/ea_emitter/element/header_line_pipeline.rb +3 -2
- data/lib/ea/svg/ea_emitter/element/header_line_provider/instance_spec.rb +10 -5
- data/lib/ea/svg/ea_emitter/element/header_line_provider/name.rb +2 -1
- data/lib/ea/svg/ea_emitter/element/header_renderer.rb +14 -12
- data/lib/ea/svg/ea_emitter/element/operation_renderer.rb +10 -7
- data/lib/ea/svg/ea_emitter/elements.rb +41 -33
- data/lib/ea/svg/ea_emitter/font_resolver.rb +14 -4
- data/lib/ea/svg/ea_emitter/ghost_labels.rb +3 -0
- data/lib/ea/svg/ea_emitter/label/end_label.rb +37 -4
- data/lib/ea/svg/ea_emitter/labels.rb +12 -4
- data/lib/ea/svg/ea_emitter/marker/diamond.rb +17 -23
- data/lib/ea/svg/ea_emitter/render_context.rb +1 -1
- data/lib/ea/svg/ea_emitter/text_renderer.rb +6 -5
- data/lib/ea/theme/definition.rb +5 -2
- data/lib/ea/theme/loader.rb +3 -2
- data/lib/ea/version.rb +1 -1
- data/memory/deviations.md +3 -0
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e5457390e5e190fab6345e9bb50a0bb773b1223e7e0617de9b8121efb6eb0cf7
|
|
4
|
+
data.tar.gz: b56bf3ce3bb1b5b8f1339f509b1ba99080f72eb480c0d85db5826c7251f91caa
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 769abf22c7038cab232e7ca4965dd44d6e11c1940d1de7cbc9f5821478109d5113d430805af6278caa34fa827167f92834400631992305f402ea38483d2e98d9
|
|
7
|
+
data.tar.gz: 19a7d924227c5783ac2e348ab6b682943746112bc3809fd32d2c1edef3c92fabdf2e74ac1de1f910c996b355825ab083cb25d20d7910ac68851801d4d5c5b401
|
data/config/themes/119.yml
CHANGED
|
@@ -17,14 +17,15 @@ label_font:
|
|
|
17
17
|
# EA renders classifier compartments at this size regardless of
|
|
18
18
|
# the diagram-level font.size.
|
|
19
19
|
element_font:
|
|
20
|
+
family: Cambria
|
|
20
21
|
size: 9
|
|
21
22
|
unit: pt
|
|
22
23
|
|
|
23
24
|
# Colors (decoded from EA's BGR integer encoding).
|
|
24
|
-
#
|
|
25
|
+
# ElemLineColor=8684698→#9A8484. Attribute rows inherit `text`: EA's
|
|
26
|
+
# exported SVGs draw them black, and QEA stores no attribute color.
|
|
25
27
|
colors:
|
|
26
28
|
text: "#000000"
|
|
27
|
-
attribute_text: "#66413F"
|
|
28
29
|
method_text: "#335C5D"
|
|
29
30
|
border: "#9A8484"
|
|
30
31
|
fill: "#FFFFFF"
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
{
|
|
2
|
+
"bold": {
|
|
3
|
+
" ": 2,
|
|
4
|
+
"!": 3,
|
|
5
|
+
"\"": 4,
|
|
6
|
+
"#": 6,
|
|
7
|
+
"$": 5,
|
|
8
|
+
"%": 10,
|
|
9
|
+
"&": 7,
|
|
10
|
+
"'": 3,
|
|
11
|
+
"(": 4,
|
|
12
|
+
")": 4,
|
|
13
|
+
"*": 5,
|
|
14
|
+
"+": 6,
|
|
15
|
+
",": 2,
|
|
16
|
+
"-": 3,
|
|
17
|
+
".": 2,
|
|
18
|
+
"/": 5,
|
|
19
|
+
"0": 6,
|
|
20
|
+
"1": 6,
|
|
21
|
+
"2": 6,
|
|
22
|
+
"3": 6,
|
|
23
|
+
"4": 6,
|
|
24
|
+
"5": 6,
|
|
25
|
+
"6": 6,
|
|
26
|
+
"7": 6,
|
|
27
|
+
"8": 6,
|
|
28
|
+
"9": 6,
|
|
29
|
+
":": 3,
|
|
30
|
+
";": 3,
|
|
31
|
+
"<": 6,
|
|
32
|
+
"=": 6,
|
|
33
|
+
">": 6,
|
|
34
|
+
"?": 5,
|
|
35
|
+
"@": 9,
|
|
36
|
+
"A": 7,
|
|
37
|
+
"B": 7,
|
|
38
|
+
"C": 6,
|
|
39
|
+
"D": 7,
|
|
40
|
+
"E": 6,
|
|
41
|
+
"F": 6,
|
|
42
|
+
"G": 6,
|
|
43
|
+
"H": 7,
|
|
44
|
+
"I": 4,
|
|
45
|
+
"J": 3,
|
|
46
|
+
"K": 7,
|
|
47
|
+
"L": 6,
|
|
48
|
+
"M": 8,
|
|
49
|
+
"N": 7,
|
|
50
|
+
"O": 7,
|
|
51
|
+
"P": 6,
|
|
52
|
+
"Q": 7,
|
|
53
|
+
"R": 7,
|
|
54
|
+
"S": 5,
|
|
55
|
+
"T": 6,
|
|
56
|
+
"U": 7,
|
|
57
|
+
"V": 6,
|
|
58
|
+
"W": 10,
|
|
59
|
+
"X": 6,
|
|
60
|
+
"Y": 6,
|
|
61
|
+
"Z": 6,
|
|
62
|
+
"[": 4,
|
|
63
|
+
"\\": 5,
|
|
64
|
+
"]": 4,
|
|
65
|
+
"^": 6,
|
|
66
|
+
"_": 4,
|
|
67
|
+
"`": 3,
|
|
68
|
+
"a": 5,
|
|
69
|
+
"b": 6,
|
|
70
|
+
"c": 5,
|
|
71
|
+
"d": 6,
|
|
72
|
+
"e": 5,
|
|
73
|
+
"f": 3,
|
|
74
|
+
"g": 5,
|
|
75
|
+
"h": 6,
|
|
76
|
+
"i": 3,
|
|
77
|
+
"j": 3,
|
|
78
|
+
"k": 6,
|
|
79
|
+
"l": 3,
|
|
80
|
+
"m": 9,
|
|
81
|
+
"n": 6,
|
|
82
|
+
"o": 6,
|
|
83
|
+
"p": 6,
|
|
84
|
+
"q": 6,
|
|
85
|
+
"r": 5,
|
|
86
|
+
"s": 5,
|
|
87
|
+
"t": 4,
|
|
88
|
+
"u": 6,
|
|
89
|
+
"v": 5,
|
|
90
|
+
"w": 8,
|
|
91
|
+
"x": 5,
|
|
92
|
+
"y": 5,
|
|
93
|
+
"z": 5,
|
|
94
|
+
"{": 4,
|
|
95
|
+
"|": 3,
|
|
96
|
+
"}": 4,
|
|
97
|
+
"~": 6
|
|
98
|
+
},
|
|
99
|
+
"italic": {
|
|
100
|
+
" ": 2,
|
|
101
|
+
"!": 3,
|
|
102
|
+
"\"": 4,
|
|
103
|
+
"#": 6,
|
|
104
|
+
"$": 5,
|
|
105
|
+
"%": 9,
|
|
106
|
+
"&": 7,
|
|
107
|
+
"'": 2,
|
|
108
|
+
"(": 4,
|
|
109
|
+
")": 4,
|
|
110
|
+
"*": 4,
|
|
111
|
+
"+": 5,
|
|
112
|
+
",": 2,
|
|
113
|
+
"-": 3,
|
|
114
|
+
".": 2,
|
|
115
|
+
"/": 5,
|
|
116
|
+
"0": 5,
|
|
117
|
+
"1": 5,
|
|
118
|
+
"2": 5,
|
|
119
|
+
"3": 5,
|
|
120
|
+
"4": 5,
|
|
121
|
+
"5": 5,
|
|
122
|
+
"6": 5,
|
|
123
|
+
"7": 5,
|
|
124
|
+
"8": 5,
|
|
125
|
+
"9": 5,
|
|
126
|
+
":": 3,
|
|
127
|
+
";": 3,
|
|
128
|
+
"<": 5,
|
|
129
|
+
"=": 5,
|
|
130
|
+
">": 5,
|
|
131
|
+
"?": 4,
|
|
132
|
+
"@": 9,
|
|
133
|
+
"A": 6,
|
|
134
|
+
"B": 6,
|
|
135
|
+
"C": 5,
|
|
136
|
+
"D": 6,
|
|
137
|
+
"E": 6,
|
|
138
|
+
"F": 5,
|
|
139
|
+
"G": 6,
|
|
140
|
+
"H": 7,
|
|
141
|
+
"I": 3,
|
|
142
|
+
"J": 3,
|
|
143
|
+
"K": 6,
|
|
144
|
+
"L": 5,
|
|
145
|
+
"M": 8,
|
|
146
|
+
"N": 7,
|
|
147
|
+
"O": 6,
|
|
148
|
+
"P": 6,
|
|
149
|
+
"Q": 6,
|
|
150
|
+
"R": 6,
|
|
151
|
+
"S": 5,
|
|
152
|
+
"T": 6,
|
|
153
|
+
"U": 6,
|
|
154
|
+
"V": 6,
|
|
155
|
+
"W": 9,
|
|
156
|
+
"X": 5,
|
|
157
|
+
"Y": 5,
|
|
158
|
+
"Z": 5,
|
|
159
|
+
"[": 3,
|
|
160
|
+
"\\": 5,
|
|
161
|
+
"]": 3,
|
|
162
|
+
"^": 5,
|
|
163
|
+
"_": 4,
|
|
164
|
+
"`": 3,
|
|
165
|
+
"a": 5,
|
|
166
|
+
"b": 5,
|
|
167
|
+
"c": 4,
|
|
168
|
+
"d": 5,
|
|
169
|
+
"e": 5,
|
|
170
|
+
"f": 3,
|
|
171
|
+
"g": 5,
|
|
172
|
+
"h": 5,
|
|
173
|
+
"i": 3,
|
|
174
|
+
"j": 3,
|
|
175
|
+
"k": 5,
|
|
176
|
+
"l": 3,
|
|
177
|
+
"m": 8,
|
|
178
|
+
"n": 5,
|
|
179
|
+
"o": 5,
|
|
180
|
+
"p": 5,
|
|
181
|
+
"q": 5,
|
|
182
|
+
"r": 4,
|
|
183
|
+
"s": 4,
|
|
184
|
+
"t": 3,
|
|
185
|
+
"u": 5,
|
|
186
|
+
"v": 5,
|
|
187
|
+
"w": 7,
|
|
188
|
+
"x": 4,
|
|
189
|
+
"y": 5,
|
|
190
|
+
"z": 4,
|
|
191
|
+
"{": 4,
|
|
192
|
+
"|": 3,
|
|
193
|
+
"}": 4,
|
|
194
|
+
"~": 5
|
|
195
|
+
},
|
|
196
|
+
"bold_italic": {
|
|
197
|
+
" ": 2,
|
|
198
|
+
"!": 3,
|
|
199
|
+
"\"": 4,
|
|
200
|
+
"#": 6,
|
|
201
|
+
"$": 5,
|
|
202
|
+
"%": 10,
|
|
203
|
+
"&": 7,
|
|
204
|
+
"'": 2,
|
|
205
|
+
"(": 4,
|
|
206
|
+
")": 4,
|
|
207
|
+
"*": 4,
|
|
208
|
+
"+": 6,
|
|
209
|
+
",": 2,
|
|
210
|
+
"-": 3,
|
|
211
|
+
".": 2,
|
|
212
|
+
"/": 5,
|
|
213
|
+
"0": 6,
|
|
214
|
+
"1": 6,
|
|
215
|
+
"2": 6,
|
|
216
|
+
"3": 6,
|
|
217
|
+
"4": 6,
|
|
218
|
+
"5": 6,
|
|
219
|
+
"6": 6,
|
|
220
|
+
"7": 6,
|
|
221
|
+
"8": 6,
|
|
222
|
+
"9": 6,
|
|
223
|
+
":": 3,
|
|
224
|
+
";": 3,
|
|
225
|
+
"<": 6,
|
|
226
|
+
"=": 6,
|
|
227
|
+
">": 6,
|
|
228
|
+
"?": 4,
|
|
229
|
+
"@": 9,
|
|
230
|
+
"A": 6,
|
|
231
|
+
"B": 6,
|
|
232
|
+
"C": 6,
|
|
233
|
+
"D": 7,
|
|
234
|
+
"E": 6,
|
|
235
|
+
"F": 5,
|
|
236
|
+
"G": 6,
|
|
237
|
+
"H": 7,
|
|
238
|
+
"I": 3,
|
|
239
|
+
"J": 3,
|
|
240
|
+
"K": 7,
|
|
241
|
+
"L": 5,
|
|
242
|
+
"M": 8,
|
|
243
|
+
"N": 7,
|
|
244
|
+
"O": 7,
|
|
245
|
+
"P": 6,
|
|
246
|
+
"Q": 7,
|
|
247
|
+
"R": 6,
|
|
248
|
+
"S": 5,
|
|
249
|
+
"T": 6,
|
|
250
|
+
"U": 7,
|
|
251
|
+
"V": 6,
|
|
252
|
+
"W": 9,
|
|
253
|
+
"X": 6,
|
|
254
|
+
"Y": 6,
|
|
255
|
+
"Z": 5,
|
|
256
|
+
"[": 4,
|
|
257
|
+
"\\": 5,
|
|
258
|
+
"]": 4,
|
|
259
|
+
"^": 6,
|
|
260
|
+
"_": 4,
|
|
261
|
+
"`": 3,
|
|
262
|
+
"a": 6,
|
|
263
|
+
"b": 6,
|
|
264
|
+
"c": 5,
|
|
265
|
+
"d": 6,
|
|
266
|
+
"e": 5,
|
|
267
|
+
"f": 3,
|
|
268
|
+
"g": 6,
|
|
269
|
+
"h": 6,
|
|
270
|
+
"i": 3,
|
|
271
|
+
"j": 3,
|
|
272
|
+
"k": 5,
|
|
273
|
+
"l": 3,
|
|
274
|
+
"m": 9,
|
|
275
|
+
"n": 6,
|
|
276
|
+
"o": 5,
|
|
277
|
+
"p": 6,
|
|
278
|
+
"q": 6,
|
|
279
|
+
"r": 5,
|
|
280
|
+
"s": 4,
|
|
281
|
+
"t": 4,
|
|
282
|
+
"u": 6,
|
|
283
|
+
"v": 5,
|
|
284
|
+
"w": 8,
|
|
285
|
+
"x": 5,
|
|
286
|
+
"y": 5,
|
|
287
|
+
"z": 5,
|
|
288
|
+
"{": 4,
|
|
289
|
+
"|": 3,
|
|
290
|
+
"}": 4,
|
|
291
|
+
"~": 6
|
|
292
|
+
}
|
|
293
|
+
}
|
data/lib/ea/fonts/metrics.rb
CHANGED
|
@@ -38,8 +38,15 @@ module Ea
|
|
|
38
38
|
CAMBRIA_7_REGULAR_WIDTHS = JSON.parse(File.read(
|
|
39
39
|
File.expand_path("cambria_7_regular_widths.json", __dir__)
|
|
40
40
|
)).freeze
|
|
41
|
+
# Styled ASCII advances come from Cambria's native TTF metrics,
|
|
42
|
+
# quantized at the same 10px logical height as EA's 7pt font.
|
|
43
|
+
# Bold uses monochrome hinted advances from the native TTF.
|
|
44
|
+
CAMBRIA_7_STYLED_WIDTHS = JSON.parse(File.read(
|
|
45
|
+
File.expand_path("cambria_7_styled_widths.json", __dir__)
|
|
46
|
+
)).freeze
|
|
41
47
|
CAMBRIA_7_GUILLEMET_PAIR_WIDTH = 10
|
|
42
|
-
private_constant :CAMBRIA_7_REGULAR_WIDTHS, :
|
|
48
|
+
private_constant :CAMBRIA_7_REGULAR_WIDTHS, :CAMBRIA_7_STYLED_WIDTHS,
|
|
49
|
+
:CAMBRIA_7_GUILLEMET_PAIR_WIDTH
|
|
43
50
|
|
|
44
51
|
# Advance used for codepoints missing from the tables.
|
|
45
52
|
DEFAULT_ADVANCE = 0.25
|
|
@@ -60,6 +67,32 @@ module Ea
|
|
|
60
67
|
|
|
61
68
|
module_function
|
|
62
69
|
|
|
70
|
+
# EA's Cambria 7pt compartment rows use a 12px cell; Carlito
|
|
71
|
+
# uses 13px. Keep unmeasured sizes and pixel fonts on their
|
|
72
|
+
# configured spacing.
|
|
73
|
+
def compartment_line_height(size, family:, unit: "pt", offset: 6, logical_height: nil)
|
|
74
|
+
return size + offset - 1 if compact_compartment_font?(size, family: family, unit: unit)
|
|
75
|
+
|
|
76
|
+
# Source sizes 90 and 100 both export as 9pt but retain
|
|
77
|
+
# distinct 12px and 13px logical heights. Apply that same
|
|
78
|
+
# source-height delta to the existing compartment spacing.
|
|
79
|
+
adjustment = if logical_height && unit == "pt" &&
|
|
80
|
+
GDI_HEIGHT_FAMILIES.include?(resolve_family(family))
|
|
81
|
+
logical_height - gdi_logical_height(size)
|
|
82
|
+
else
|
|
83
|
+
0
|
|
84
|
+
end
|
|
85
|
+
size + offset + adjustment
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def gdi_logical_height(size)
|
|
89
|
+
(size.to_f * 10 / 7).round
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def compact_compartment_font?(size, family:, unit: "pt")
|
|
93
|
+
family.to_s.casecmp("Cambria").zero? && size == 7 && unit == "pt"
|
|
94
|
+
end
|
|
95
|
+
|
|
63
96
|
# Width of `text` in EA's exported SVG units for the given pt
|
|
64
97
|
# size, or nil when the family is not metric-compatible.
|
|
65
98
|
#
|
|
@@ -69,19 +102,19 @@ module Ea
|
|
|
69
102
|
# textLength is the SUM of those per-glyph integers - corpus
|
|
70
103
|
# fitted to 97-100% exact on 10k Carlito texts (bold 7pt
|
|
71
104
|
# 1666/1671). The legacy sum-then-round model matched only 27%.
|
|
72
|
-
def text_length(text, size_pt, family: nil, weight: nil, style: nil)
|
|
105
|
+
def text_length(text, size_pt, family: nil, weight: nil, style: nil, logical_height: nil)
|
|
73
106
|
return nil unless supported_family?(family)
|
|
74
107
|
return nil if text.nil? || text.empty?
|
|
75
108
|
|
|
76
109
|
return cambria_text_length(text, size_pt, weight, style) if resolve_family(family) == "Cambria"
|
|
77
110
|
|
|
78
|
-
advance_text_length(text, size_pt, family, weight, style)
|
|
111
|
+
advance_text_length(text, size_pt, family, weight, style, logical_height)
|
|
79
112
|
end
|
|
80
113
|
|
|
81
|
-
def advance_text_length(text, size_pt, family, weight, style)
|
|
114
|
+
def advance_text_length(text, size_pt, family, weight, style, logical_height)
|
|
82
115
|
resolved = resolve_family(family)
|
|
83
116
|
if GDI_HEIGHT_FAMILIES.include?(resolved)
|
|
84
|
-
height = (size_pt
|
|
117
|
+
height = logical_height || gdi_logical_height(size_pt)
|
|
85
118
|
table = table_for(family, weight, style)
|
|
86
119
|
latin = 0
|
|
87
120
|
cjk_count = 0
|
|
@@ -105,8 +138,11 @@ module Ea
|
|
|
105
138
|
|
|
106
139
|
def cambria_text_length(text, size_pt, weight, style)
|
|
107
140
|
return nil unless size_pt.to_f.to_r == 7
|
|
108
|
-
return nil unless ["", "0", "400", "normal"].include?(weight.to_s)
|
|
109
|
-
return nil unless ["", "normal"].include?(style.to_s)
|
|
141
|
+
return nil unless ["", "0", "400", "normal", "700", "bold"].include?(weight.to_s)
|
|
142
|
+
return nil unless ["", "normal", "italic"].include?(style.to_s)
|
|
143
|
+
|
|
144
|
+
key = STYLE_KEYS.fetch([bold?(weight), italic?(style)])
|
|
145
|
+
return cambria_styled_length(text, key) unless key == :regular
|
|
110
146
|
|
|
111
147
|
return cambria_ascii_length(text) unless text.start_with?("«") && text.end_with?("»")
|
|
112
148
|
|
|
@@ -115,6 +151,13 @@ module Ea
|
|
|
115
151
|
end
|
|
116
152
|
private_class_method :cambria_text_length
|
|
117
153
|
|
|
154
|
+
def cambria_styled_length(text, key)
|
|
155
|
+
table = CAMBRIA_7_STYLED_WIDTHS.fetch(key.to_s)
|
|
156
|
+
widths = text.each_char.map { |char| table[char] }
|
|
157
|
+
widths.sum unless widths.empty? || widths.include?(nil)
|
|
158
|
+
end
|
|
159
|
+
private_class_method :cambria_styled_length
|
|
160
|
+
|
|
118
161
|
def cambria_ascii_length(text)
|
|
119
162
|
widths = text.each_char.map { |char| CAMBRIA_7_REGULAR_WIDTHS[char] }
|
|
120
163
|
widths.sum unless widths.empty? || widths.include?(nil)
|
|
@@ -22,6 +22,8 @@ module Ea
|
|
|
22
22
|
attribute :line_width, :integer
|
|
23
23
|
attribute :font_family, :string
|
|
24
24
|
attribute :font_size, :integer
|
|
25
|
+
attribute :font_logical_height, :integer # original GDI pixel height before point normalization
|
|
26
|
+
attribute :font_size_unit, :string # source font size unit, before theme fallback
|
|
25
27
|
attribute :font_bold, :boolean
|
|
26
28
|
attribute :font_italic, :boolean
|
|
27
29
|
attribute :font_underline, :boolean
|
|
@@ -53,6 +55,8 @@ module Ea
|
|
|
53
55
|
map "lineWidth", to: :line_width
|
|
54
56
|
map "fontFamily", to: :font_family
|
|
55
57
|
map "fontSize", to: :font_size
|
|
58
|
+
map "fontSizeUnit", to: :font_size_unit
|
|
59
|
+
map "fontLogicalHeight", to: :font_logical_height
|
|
56
60
|
map "fontBold", to: :font_bold, render_default: true
|
|
57
61
|
map "fontItalic", to: :font_italic, render_default: true
|
|
58
62
|
map "fontUnderline", to: :font_underline, render_default: true
|
|
@@ -8,12 +8,118 @@ module Ea
|
|
|
8
8
|
module AutoRouting
|
|
9
9
|
private
|
|
10
10
|
|
|
11
|
-
#
|
|
11
|
+
# An empty flush LV tree uses the target's center x and source's
|
|
12
|
+
# center y for its corner, the transposed LH construction. Keep
|
|
13
|
+
# authored corners and other connector styles on their existing path.
|
|
14
|
+
def derived_vertical_lateral_route(style, link_row, connector, source, target)
|
|
15
|
+
return nil unless style[:mode] == "3" && style[:lwidth] == "0" &&
|
|
16
|
+
connector.connector_type == "Association" && link_row.path.to_s.empty?
|
|
17
|
+
|
|
18
|
+
corner = [target.x + target.width / 2, source.y + source.height / 2]
|
|
19
|
+
vertical_corner_route(corner, source, target)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Without an authored Path, a direct Association can still store
|
|
23
|
+
# aligned mathematical origins rather than screen-space ports.
|
|
24
|
+
def stored_association_path?(model, link_row, source, target, geom)
|
|
25
|
+
return true unless model.connector_type == "Association" && link_row.path.to_s.empty?
|
|
26
|
+
return false unless geom.values_at(:sx, :sy, :ex, :ey).all?
|
|
27
|
+
|
|
28
|
+
origins = [stored_ray_origin(source, geom[:sx], geom[:sy]),
|
|
29
|
+
stored_ray_origin(target, geom[:ex], geom[:ey])]
|
|
30
|
+
origins.first.y == origins.last.y && horizontal_ray_gap?(source, target) &&
|
|
31
|
+
origins.zip([source, target]).all? { |origin, box| inside_box?(origin, grown_bounds(box, 1)) }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# A Mode 2 Association with equal SX/EX (vertical EDGE 1/3) or
|
|
35
|
+
# equal SY/EY (horizontal EDGE 2/4) and no custom Path docks as a
|
|
36
|
+
# straight line through the middle of the boxes' overlap, moved
|
|
37
|
+
# by the shared stored offset. Stored SY counts upward.
|
|
38
|
+
def overlap_ray_origins(model, link_row, diagram_row, geom, context)
|
|
39
|
+
return model if context
|
|
40
|
+
return model unless overlap_ray_candidate?(model, link_row, geom)
|
|
41
|
+
|
|
42
|
+
source, target = stored_ray_boxes(link_row, diagram_row)
|
|
43
|
+
origins = overlap_origins(source, target, geom)
|
|
44
|
+
return model unless origins && overlap_correction?(source, target, geom)
|
|
45
|
+
|
|
46
|
+
model.source_anchor, model.target_anchor = origins.map do |ox, oy|
|
|
47
|
+
Ea::Model::Point.new(x: ox, y: oy)
|
|
48
|
+
end
|
|
49
|
+
if model.waypoints.size == 3
|
|
50
|
+
model.waypoints = port_waypoints(model, [model.source_anchor, model.target_anchor])
|
|
51
|
+
end
|
|
52
|
+
model.style[:overlap_ray] = true
|
|
53
|
+
model
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Aligned, zero-offset EDGE 2 routes already have their legacy docks.
|
|
57
|
+
def overlap_correction?(source, target, geom)
|
|
58
|
+
geom[:edge] != 2 || !geom[:sy].zero? ||
|
|
59
|
+
source.y * 2 + source.height != target.y * 2 + target.height
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Both ray origins share the overlap coordinate on the axis the
|
|
63
|
+
# offsets move and sit at each box's centre on the other axis.
|
|
64
|
+
def overlap_origins(source, target, geom)
|
|
65
|
+
along, across, offset = overlap_axes(geom)
|
|
66
|
+
coordinate = overlap_ray_coordinate(*box_span(source, along), *box_span(target, along), offset)
|
|
67
|
+
return nil unless coordinate && disjoint_span?(*box_span(source, across), *box_span(target, across))
|
|
68
|
+
|
|
69
|
+
[source, target].map do |box|
|
|
70
|
+
start, size = box_span(box, across)
|
|
71
|
+
centre = start + size / 2.0
|
|
72
|
+
[2, 4].include?(geom[:edge]) ? [centre, coordinate] : [coordinate, centre]
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# [axis the offsets move, axis the boxes face, stored offset]
|
|
77
|
+
def overlap_axes(geom)
|
|
78
|
+
return [%i[y height], %i[x width], -geom[:sy]] if [2, 4].include?(geom[:edge])
|
|
79
|
+
|
|
80
|
+
[%i[x width], %i[y height], geom[:sx]]
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def box_span(box, keys)
|
|
84
|
+
keys.map { |key| box.public_send(key) }
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def overlap_ray_candidate?(model, link_row, geom)
|
|
88
|
+
model.connector_type == "Association" && model.style[:mode] == "2" &&
|
|
89
|
+
[2, 3].include?(model.waypoints.size) && link_row.path.to_s.empty? && !model.style[:tree] &&
|
|
90
|
+
model.has_geometry_offsets && uniform_overlap_offsets?(geom)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def uniform_overlap_offsets?(geom)
|
|
94
|
+
return false unless geom.values_at(:sx, :sy, :ex, :ey).all?
|
|
95
|
+
|
|
96
|
+
if [2, 4].include?(geom[:edge])
|
|
97
|
+
geom[:sy] == geom[:ey]
|
|
98
|
+
else
|
|
99
|
+
[1, 3].include?(geom[:edge]) && geom[:sx] == geom[:ex]
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Floor of the overlap midpoint plus the shared offset; nil
|
|
104
|
+
# when the boxes do not overlap on this axis.
|
|
105
|
+
def overlap_ray_coordinate(start_a, size_a, start_b, size_b, offset)
|
|
106
|
+
low = [start_a, start_b].max
|
|
107
|
+
high = [start_a + size_a, start_b + size_b].min
|
|
108
|
+
return nil if low > high
|
|
109
|
+
|
|
110
|
+
coordinate = ((low + high) / 2.0 + offset).floor
|
|
111
|
+
coordinate if coordinate.between?(low, high)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Nonuniform horizontal offsets move the shared overlap row by SY.
|
|
12
115
|
# Uniform offsets retain the shared-overlap routing contract.
|
|
13
116
|
def source_row_association_ray(model, link_row, diagram_row, geom, context)
|
|
14
|
-
return model if context || !source_row_association?(model,
|
|
117
|
+
return model if context || !source_row_association?(model, geom)
|
|
15
118
|
|
|
16
|
-
|
|
119
|
+
source, target = stored_ray_boxes(link_row, diagram_row)
|
|
120
|
+
return model unless source_row_path?(link_row, source, target)
|
|
121
|
+
|
|
122
|
+
origins = source_row_origins(source, target, geom)
|
|
17
123
|
return model unless origins
|
|
18
124
|
|
|
19
125
|
model.source_anchor, model.target_anchor = origins
|
|
@@ -72,16 +178,12 @@ module Ea
|
|
|
72
178
|
def source_row_origins(source, target, geom)
|
|
73
179
|
return nil unless source_row_faces_target?(source, target, geom[:edge])
|
|
74
180
|
|
|
75
|
-
row =
|
|
76
|
-
return nil unless [source, target].all? { |box| within_height?(row, box) }
|
|
181
|
+
row = overlap_ray_coordinate(source.y, source.height, target.y, target.height, -geom[:sy])
|
|
182
|
+
return nil unless row && [source, target].all? { |box| within_height?(row, box) }
|
|
77
183
|
|
|
78
184
|
[source, target].map { |box| Ea::Model::Point.new(x: box.x + box.width / 2.0, y: row) }
|
|
79
185
|
end
|
|
80
186
|
|
|
81
|
-
def source_row_y(source, geom)
|
|
82
|
-
(source.y + source.height / 2.0 - geom[:sy]).floor
|
|
83
|
-
end
|
|
84
|
-
|
|
85
187
|
def source_row_faces_target?(source, target, edge)
|
|
86
188
|
if edge == 2
|
|
87
189
|
source.x + source.width < target.x
|
|
@@ -90,13 +192,24 @@ module Ea
|
|
|
90
192
|
end
|
|
91
193
|
end
|
|
92
194
|
|
|
93
|
-
def source_row_association?(model,
|
|
195
|
+
def source_row_association?(model, geom)
|
|
94
196
|
model.connector_type == "Association" && model.has_geometry_offsets &&
|
|
95
197
|
model.source_anchor && model.target_anchor &&
|
|
96
|
-
|
|
198
|
+
source_row_style?(model.style.to_h) &&
|
|
97
199
|
source_row_offsets?(geom)
|
|
98
200
|
end
|
|
99
201
|
|
|
202
|
+
def source_row_path?(link_row, source, target)
|
|
203
|
+
link_row.path.to_s.empty? || stale_horizontal_jog?(link_row, source, target)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# A horizontal saved jog outside both boxes is stale auto-routing.
|
|
207
|
+
# A corner that still lies beside either box remains authored.
|
|
208
|
+
def stale_horizontal_jog?(link_row, source, target)
|
|
209
|
+
corners = intermediate_waypoints(link_row)
|
|
210
|
+
horizontal_jog?(corners) && [source, target].none? { |box| within_height?(corners.first[1], box) }
|
|
211
|
+
end
|
|
212
|
+
|
|
100
213
|
def source_row_style?(style)
|
|
101
214
|
style[:mode] == "2" && style[:lwidth] == "2" && !style[:tree]
|
|
102
215
|
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Sources
|
|
5
|
+
module Qea
|
|
6
|
+
class DiagramBuilder
|
|
7
|
+
# Resolves saved classifier and placement fonts and EA's point sizes.
|
|
8
|
+
module FontSettings
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
# Placement defaults inherit the classifier's saved font before the
|
|
12
|
+
# diagram theme. A zero fontsz is EA's use-default marker.
|
|
13
|
+
def effective_font_style(obj_row, style)
|
|
14
|
+
object = database.find_object(obj_row.ea_object_id)
|
|
15
|
+
defaults = DiagramStyleParser.parse(object&.styleex)
|
|
16
|
+
{
|
|
17
|
+
font: presence(style[:font]) || defaults[:font],
|
|
18
|
+
fontsz: font_size_from(style) ? style[:fontsz] : defaults[:fontsz]
|
|
19
|
+
}
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def presence(value)
|
|
23
|
+
value.nil? || value.to_s.empty? ? nil : value
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# EA stores the element font size in tenths of a point and
|
|
27
|
+
# renders it through GDI's double rounding: pixel height =
|
|
28
|
+
# floor(tenths * 96 / 720), then point size =
|
|
29
|
+
# floor(pixels * 72 / 96). A zero fontsz inherits the default.
|
|
30
|
+
USE_DEFAULT_FONTSZ = [0].freeze
|
|
31
|
+
GDI_DPI = 96
|
|
32
|
+
|
|
33
|
+
def font_logical_height_from(style_hash)
|
|
34
|
+
raw = style_hash[:fontsz]
|
|
35
|
+
return nil if raw.nil? || raw.to_s.empty?
|
|
36
|
+
|
|
37
|
+
return nil if USE_DEFAULT_FONTSZ.include?(raw.to_i)
|
|
38
|
+
|
|
39
|
+
(raw.to_f * GDI_DPI / 720).floor
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def font_size_from(style_hash)
|
|
43
|
+
pixels = font_logical_height_from(style_hash)
|
|
44
|
+
(pixels * 72.0 / GDI_DPI).floor if pixels
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|