1 | // SPDX-License-Identifier: MIT OR MPL-2.0 OR LGPL-2.1-or-later OR GPL-2.0-or-later |
2 | // Copyright 2012, SIL International, All rights reserved. |
3 | |
4 | |
5 | #include "inc/Segment.h" |
6 | #include "graphite2/Font.h" |
7 | #include "inc/debug.h" |
8 | #include "inc/CharInfo.h" |
9 | #include "inc/Slot.h" |
10 | #include "inc/Main.h" |
11 | #include <cmath> |
12 | |
13 | using namespace graphite2; |
14 | |
15 | class JustifyTotal { |
16 | public: |
17 | JustifyTotal() : m_numGlyphs(0), m_tStretch(0), m_tShrink(0), m_tStep(0), m_tWeight(0) {} |
18 | void accumulate(Slot *s, Segment *seg, int level); |
19 | int weight() const { return m_tWeight; } |
20 | |
21 | CLASS_NEW_DELETE |
22 | |
23 | private: |
24 | int m_numGlyphs; |
25 | int m_tStretch; |
26 | int m_tShrink; |
27 | int m_tStep; |
28 | int m_tWeight; |
29 | }; |
30 | |
31 | void JustifyTotal::accumulate(Slot *s, Segment *seg, int level) |
32 | { |
33 | ++m_numGlyphs; |
34 | m_tStretch += s->getJustify(seg, level, 0); |
35 | m_tShrink += s->getJustify(seg, level, 1); |
36 | m_tStep += s->getJustify(seg, level, 2); |
37 | m_tWeight += s->getJustify(seg, level, 3); |
38 | } |
39 | |
40 | float Segment::justify(Slot *pSlot, const Font *font, float width, GR_MAYBE_UNUSED justFlags jflags, Slot *pFirst, Slot *pLast) |
41 | { |
42 | Slot *end = last(); |
43 | float currWidth = 0.0; |
44 | const float scale = font ? font->scale() : 1.0f; |
45 | Position res; |
46 | |
47 | if (width < 0 && !(silf()->flags())) |
48 | return width; |
49 | |
50 | if ((m_dir & 1) != m_silf->dir() && m_silf->bidiPass() != m_silf->numPasses()) |
51 | { |
52 | reverseSlots(); |
53 | std::swap(pFirst, pLast); |
54 | } |
55 | if (!pFirst) pFirst = pSlot; |
56 | while (!pFirst->isBase()) pFirst = pFirst->attachedTo(); |
57 | if (!pLast) pLast = last(); |
58 | while (!pLast->isBase()) pLast = pLast->attachedTo(); |
59 | const float base = pFirst->origin().x / scale; |
60 | width = width / scale; |
61 | if ((jflags & gr_justEndInline) == 0) |
62 | { |
63 | while (pLast != pFirst && pLast) |
64 | { |
65 | Rect bbox = theGlyphBBoxTemporary(pLast->glyph()); |
66 | if (bbox.bl.x != 0.f || bbox.bl.y != 0.f || bbox.tr.x != 0.f || bbox.tr.y == 0.f) |
67 | break; |
68 | pLast = pLast->prev(); |
69 | } |
70 | } |
71 | |
72 | if (pLast) |
73 | end = pLast->nextSibling(); |
74 | if (pFirst) |
75 | pFirst = pFirst->nextSibling(); |
76 | |
77 | int icount = 0; |
78 | int numLevels = silf()->numJustLevels(); |
79 | if (!numLevels) |
80 | { |
81 | for (Slot *s = pSlot; s && s != end; s = s->nextSibling()) |
82 | { |
83 | CharInfo *c = charinfo(s->before()); |
84 | if (isWhitespace(c->unicodeChar())) |
85 | { |
86 | s->setJustify(this, 0, 3, 1); |
87 | s->setJustify(this, 0, 2, 1); |
88 | s->setJustify(this, 0, 0, -1); |
89 | ++icount; |
90 | } |
91 | } |
92 | if (!icount) |
93 | { |
94 | for (Slot *s = pSlot; s && s != end; s = s->nextSibling()) |
95 | { |
96 | s->setJustify(this, 0, 3, 1); |
97 | s->setJustify(this, 0, 2, 1); |
98 | s->setJustify(this, 0, 0, -1); |
99 | } |
100 | } |
101 | ++numLevels; |
102 | } |
103 | |
104 | Vector<JustifyTotal> stats(numLevels); |
105 | for (Slot *s = pFirst; s && s != end; s = s->nextSibling()) |
106 | { |
107 | float w = s->origin().x / scale + s->advance() - base; |
108 | if (w > currWidth) currWidth = w; |
109 | for (int j = 0; j < numLevels; ++j) |
110 | stats[j].accumulate(s, this, j); |
111 | s->just(0); |
112 | } |
113 | |
114 | for (int i = (width < 0.0f) ? -1 : numLevels - 1; i >= 0; --i) |
115 | { |
116 | float diff; |
117 | float error = 0.; |
118 | float diffpw; |
119 | int tWeight = stats[i].weight(); |
120 | if (tWeight == 0) continue; |
121 | |
122 | do { |
123 | error = 0.; |
124 | diff = width - currWidth; |
125 | diffpw = diff / tWeight; |
126 | tWeight = 0; |
127 | for (Slot *s = pFirst; s && s != end; s = s->nextSibling()) // don't include final glyph |
128 | { |
129 | int w = s->getJustify(this, i, 3); |
130 | float pref = diffpw * w + error; |
131 | int step = s->getJustify(this, i, 2); |
132 | if (!step) step = 1; // handle lazy font developers |
133 | if (pref > 0) |
134 | { |
135 | float max = uint16(s->getJustify(this, i, 0)); |
136 | if (i == 0) max -= s->just(); |
137 | if (pref > max) pref = max; |
138 | else tWeight += w; |
139 | } |
140 | else |
141 | { |
142 | float max = uint16(s->getJustify(this, i, 1)); |
143 | if (i == 0) max += s->just(); |
144 | if (-pref > max) pref = -max; |
145 | else tWeight += w; |
146 | } |
147 | int actual = int(pref / step) * step; |
148 | |
149 | if (actual) |
150 | { |
151 | error += diffpw * w - actual; |
152 | if (i == 0) |
153 | s->just(s->just() + actual); |
154 | else |
155 | s->setJustify(this, i, 4, actual); |
156 | } |
157 | } |
158 | currWidth += diff - error; |
159 | } while (i == 0 && int(std::abs(error)) > 0 && tWeight); |
160 | } |
161 | |
162 | Slot *oldFirst = m_first; |
163 | Slot *oldLast = m_last; |
164 | if (silf()->flags() & 1) |
165 | { |
166 | m_first = pSlot = addLineEnd(pSlot); |
167 | m_last = pLast = addLineEnd(end); |
168 | if (!m_first || !m_last) return -1.0; |
169 | } |
170 | else |
171 | { |
172 | m_first = pSlot; |
173 | m_last = pLast; |
174 | } |
175 | |
176 | // run justification passes here |
177 | #if !defined GRAPHITE2_NTRACING |
178 | json * const dbgout = m_face->logger(); |
179 | if (dbgout) |
180 | *dbgout << json::object |
181 | << "justifies" << objectid(this) |
182 | << "passes" << json::array; |
183 | #endif |
184 | |
185 | if (m_silf->justificationPass() != m_silf->positionPass() && (width >= 0.f || (silf()->flags() & 1))) |
186 | m_silf->runGraphite(this, m_silf->justificationPass(), m_silf->positionPass()); |
187 | |
188 | #if !defined GRAPHITE2_NTRACING |
189 | if (dbgout) |
190 | { |
191 | *dbgout << json::item << json::close; // Close up the passes array |
192 | positionSlots(NULL, pSlot, pLast, m_dir); |
193 | Slot *lEnd = pLast->nextSibling(); |
194 | *dbgout << "output" << json::array; |
195 | for(Slot * t = pSlot; t != lEnd; t = t->next()) |
196 | *dbgout << dslot(this, t); |
197 | *dbgout << json::close << json::close; |
198 | } |
199 | #endif |
200 | |
201 | res = positionSlots(font, pSlot, pLast, m_dir); |
202 | |
203 | if (silf()->flags() & 1) |
204 | { |
205 | if (m_first) |
206 | delLineEnd(m_first); |
207 | if (m_last) |
208 | delLineEnd(m_last); |
209 | } |
210 | m_first = oldFirst; |
211 | m_last = oldLast; |
212 | |
213 | if ((m_dir & 1) != m_silf->dir() && m_silf->bidiPass() != m_silf->numPasses()) |
214 | reverseSlots(); |
215 | return res.x; |
216 | } |
217 | |
218 | Slot *Segment::addLineEnd(Slot *nSlot) |
219 | { |
220 | Slot *eSlot = newSlot(); |
221 | if (!eSlot) return NULL; |
222 | const uint16 gid = silf()->endLineGlyphid(); |
223 | const GlyphFace * theGlyph = m_face->glyphs().glyphSafe(gid); |
224 | eSlot->setGlyph(this, gid, theGlyph); |
225 | if (nSlot) |
226 | { |
227 | eSlot->next(nSlot); |
228 | eSlot->prev(nSlot->prev()); |
229 | nSlot->prev(eSlot); |
230 | eSlot->before(nSlot->before()); |
231 | if (eSlot->prev()) |
232 | eSlot->after(eSlot->prev()->after()); |
233 | else |
234 | eSlot->after(nSlot->before()); |
235 | } |
236 | else |
237 | { |
238 | nSlot = m_last; |
239 | eSlot->prev(nSlot); |
240 | nSlot->next(eSlot); |
241 | eSlot->after(eSlot->prev()->after()); |
242 | eSlot->before(nSlot->after()); |
243 | } |
244 | return eSlot; |
245 | } |
246 | |
247 | void Segment::delLineEnd(Slot *s) |
248 | { |
249 | Slot *nSlot = s->next(); |
250 | if (nSlot) |
251 | { |
252 | nSlot->prev(s->prev()); |
253 | if (s->prev()) |
254 | s->prev()->next(nSlot); |
255 | } |
256 | else |
257 | s->prev()->next(NULL); |
258 | freeSlot(s); |
259 | } |
260 | |