1 package org.daisy.pipeline.braille.common;
2
3 import java.util.ArrayList;
4 import java.util.Collections;
5 import java.util.HashMap;
6 import java.util.Iterator;
7 import java.util.List;
8 import java.util.Map;
9 import java.util.NoSuchElementException;
10 import java.util.function.Supplier;
11
12 import org.daisy.braille.css.SimpleInlineStyle;
13 import org.daisy.braille.css.BrailleCSSProperty.Hyphens;
14 import org.daisy.braille.css.BrailleCSSProperty.TextTransform;
15 import org.daisy.dotify.api.translator.UnsupportedMetricException;
16 import org.daisy.pipeline.braille.common.Hyphenator.NonStandardHyphenationException;
17 import org.daisy.pipeline.braille.css.CSSStyledText;
18
19 import com.google.common.base.MoreObjects;
20 import com.google.common.base.MoreObjects.ToStringHelper;
21 import com.google.common.collect.Iterables;
22 import com.google.common.collect.Maps;
23
24 import cz.vutbr.web.css.CSSProperty;
25 import cz.vutbr.web.css.Term;
26 import cz.vutbr.web.css.TermIdent;
27 import cz.vutbr.web.css.TermList;
28
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
31
32
33
34
35 public class CompoundBrailleTranslator extends AbstractBrailleTranslator {
36
37 private final Map<String,BrailleTranslator> translators;
38 private final boolean implementsFromStyledTextToBraille;
39 private final boolean implementsLineBreakingFromStyledText;
40
41 public CompoundBrailleTranslator(BrailleTranslator mainTranslator, Map<String,Supplier<BrailleTranslator>> subTranslators) {
42 if (subTranslators.containsKey("auto") || mainTranslator == null)
43 throw new IllegalArgumentException();
44 translators = new HashMap<>();
45 translators.put("auto", mainTranslator);
46 for (Map.Entry<String,Supplier<BrailleTranslator>> t : subTranslators.entrySet())
47 try {
48 translators.put(t.getKey(), t.getValue().get());
49 } catch (NoSuchElementException e) {
50 logger.warn("No braille translator found for handling text-transform '" + t.getKey() + "'");
51 }
52 implementsFromStyledTextToBraille = Iterables.all(
53 translators.values(),
54 t -> {
55 try {
56 t.fromStyledTextToBraille();
57 return true; }
58 catch (UnsupportedOperationException e) {
59 return false; }} );
60 implementsLineBreakingFromStyledText = Iterables.all(
61 translators.values(),
62 t -> {
63 try {
64 t.lineBreakingFromStyledText();
65 return true; }
66 catch (UnsupportedOperationException e) {
67 return false; }} );
68 }
69
70 protected CompoundBrailleTranslator(CompoundBrailleTranslator from) {
71 super(from);
72 this.translators = from.translators;
73 this.implementsFromStyledTextToBraille = from.implementsFromStyledTextToBraille;
74 this.implementsLineBreakingFromStyledText = from.implementsLineBreakingFromStyledText;
75 }
76
77 @Override
78 public ToStringHelper toStringHelper() {
79 return MoreObjects.toStringHelper("CompoundBrailleTranslator")
80 .add("translators", translators);
81 }
82
83
84
85
86
87 public CompoundBrailleTranslator _withHyphenator(Hyphenator hyphenator) {
88 Map<String,BrailleTranslator> translatorsWithHyphenator = new HashMap<>(); {
89 for (String k : translators.keySet())
90 translatorsWithHyphenator.put(k, translators.get(k).withHyphenator(hyphenator));
91 }
92 return new CompoundBrailleTranslator(translatorsWithHyphenator.remove("auto"),
93 Maps.transformValues(translatorsWithHyphenator, x -> (() -> x)));
94 }
95
96 private static abstract class TransformImpl<T> {
97
98 abstract Iterable<T> transform(Iterable<CSSStyledText> styledText, int from, int to, String textTransform);
99 abstract Iterable<CSSStyledText> transformContext(Iterable<CSSStyledText> styledText, int from, int to, String textTransform);
100
101 abstract boolean supports(String textTransform);
102
103 List<T> transform(Iterable<CSSStyledText> styledText, int from, int to) {
104 if (to < 0)
105 to = Iterables.size(styledText);
106 if (from < 0 || from > to)
107 throw new IndexOutOfBoundsException();
108 List<T> transformed = new ArrayList<>();
109 if (from == to) return transformed;
110 List<CSSStyledText> styledTextList = new ArrayList<>();
111 for (CSSStyledText t : styledText)
112 styledTextList.add(t.clone());
113
114 List<CSSStyledText> buffer = new ArrayList<CSSStyledText>();
115
116 List<CSSStyledText> context = new ArrayList<CSSStyledText>();
117 String textTransform = "auto";
118 for (int i = styledTextList.size(); i >= 0; i--) {
119 CSSStyledText next;
120 String nextTextTransform; {
121 if (i == 0) {
122 next = null;
123 nextTextTransform = null;
124 } else {
125 next = styledTextList.get(i - 1);
126 nextTextTransform = "auto";
127 SimpleInlineStyle style = next.getStyle();
128 if (style != null) {
129 CSSProperty val = style.getProperty("text-transform");
130 if (val != null) {
131 if (val == TextTransform.NONE) {
132 if (supports("none")) {
133 nextTextTransform = "none";
134 style.removeProperty("text-transform"); }
135 } else if (val == TextTransform.list_values) {
136 TermList values = style.getValue(TermList.class, "text-transform");
137
138
139
140
141
142
143
144 Iterator<Term<?>> it = values.iterator();
145 while (it.hasNext()) {
146 String tt = ((TermIdent)it.next()).getValue();
147 if (supports(tt)) {
148 nextTextTransform = tt;
149 it.remove();
150 break; }}
151 if (values.isEmpty())
152 style.removeProperty("text-transform"); }}}
153 }
154 }
155 if (next == null || (nextTextTransform != textTransform && buffer.size() > 0)) {
156 if (i < to) {
157 int j = 0;
158 for (T t : transform(org.daisy.pipeline.braille.common.util.Iterables.clone(
159 Iterables.concat(buffer, context)),
160 from > i ? from - i : 0,
161 to < i + buffer.size() ? to - i : buffer.size(),
162 textTransform))
163 transformed.add(j++, t);
164 }
165 if (i <= from)
166 return transformed;
167 else {
168
169 try {
170 int j = 0;
171 for (CSSStyledText t : transformContext(org.daisy.pipeline.braille.common.util.Iterables.clone(
172 Iterables.concat(buffer, context)),
173 from > i ? from - i : 0,
174 buffer.size(),
175 textTransform))
176 context.add(j++, t);
177 buffer.clear();
178 } catch (NonStandardHyphenationException e) {
179
180 for (int j = buffer.size() - 1; j >= 0; j--) {
181 CSSStyledText st = (CSSStyledText)buffer.get(j).clone();
182 SimpleInlineStyle style = st.getStyle();
183 if (style != null && style.getProperty("hyphens") == Hyphens.AUTO)
184 style.removeProperty("hyphens");
185 buffer.set(j, st);
186 context.add(0, transformContext(org.daisy.pipeline.braille.common.util.Iterables.clone(
187 Iterables.concat(buffer, context)),
188 j,
189 j + 1,
190 textTransform).iterator().next());
191 buffer.remove(j);
192 }
193 }
194 }
195 }
196 if (next != null) {
197 buffer.add(0, next);
198 textTransform = nextTextTransform;
199 }
200 }
201 return transformed;
202 }
203 }
204
205 private FromStyledTextToBraille fromStyledTextToBraille = null;
206
207
208
209
210
211 @Override
212 public FromStyledTextToBraille fromStyledTextToBraille() {
213 if (!implementsFromStyledTextToBraille)
214 throw new UnsupportedOperationException();
215 if (fromStyledTextToBraille == null)
216 fromStyledTextToBraille = new FromStyledTextToBraille() {
217 TransformImpl<CSSStyledText> impl = new TransformImpl<CSSStyledText>() {
218 Iterable<CSSStyledText> transform(Iterable<CSSStyledText> styledText, int from, int to, String textTransform) {
219 return translators.get(textTransform).fromStyledTextToBraille().transform(styledText, from, to);
220 }
221 Iterable<CSSStyledText> transformContext(Iterable<CSSStyledText> styledText, int from, int to, String textTransform) {
222 return transform(styledText, from, to, textTransform);
223 }
224 boolean supports(String textTransform) {
225 return translators.containsKey(textTransform);
226 }
227 };
228 public Iterable<CSSStyledText> transform(Iterable<CSSStyledText> styledText, int from, int to) {
229 return impl.transform(styledText, from, to);
230 }
231 @Override
232 public String toString() {
233 return CompoundBrailleTranslator.this.toString();
234 }
235 };
236 return fromStyledTextToBraille;
237 }
238
239 private LineBreakingFromStyledText lineBreakingFromStyledText = null;
240
241
242
243
244
245 @Override
246 public LineBreakingFromStyledText lineBreakingFromStyledText() {
247 if (!implementsLineBreakingFromStyledText)
248 throw new UnsupportedOperationException();
249 if (lineBreakingFromStyledText == null)
250 lineBreakingFromStyledText = new LineBreakingFromStyledText() {
251 TransformImpl<LineIterator> impl = new TransformImpl<LineIterator>() {
252 Iterable<LineIterator> transform(Iterable<CSSStyledText> styledText, int from, int to, String textTransform) {
253 return Collections.singleton(
254 translators.get(textTransform).lineBreakingFromStyledText().transform(styledText, from, to));
255 }
256 Iterable<CSSStyledText> transformContext(Iterable<CSSStyledText> styledText, int from, int to, String textTransform) {
257 return translators.get(textTransform).fromStyledTextToBraille().transform(styledText, from, to);
258 }
259 boolean supports(String textTransform) {
260 return translators.containsKey(textTransform);
261 }
262 };
263 public LineIterator transform(Iterable<CSSStyledText> styledText, int from, int to) {
264 return concatLineIterators(impl.transform(styledText, from, to));
265 }
266 @Override
267 public String toString() {
268 return CompoundBrailleTranslator.this.toString();
269 }
270 };
271 return lineBreakingFromStyledText;
272 }
273
274
275
276
277
278
279 static BrailleTranslator.LineIterator concatLineIterators(List<BrailleTranslator.LineIterator> iterators) {
280 if (iterators.size() == 0)
281 return new BrailleTranslator.LineIterator() {
282 public String nextTranslatedRow(int limit, boolean force, boolean wholeWordsOnly) {
283 return "";
284 }
285 public String getTranslatedRemainder() {
286 return "";
287 }
288 public int countRemaining() {
289 return 0;
290 }
291 public boolean hasNext() {
292 return false;
293 }
294 public BrailleTranslator.LineIterator copy() {
295 return this;
296 }
297 public boolean supportsMetric(String metric) {
298 return false;
299 }
300 public double getMetric(String metric) {
301 throw new UnsupportedMetricException("Metric not supported: " + metric);
302 }
303 };
304 else if (iterators.size() == 1 && iterators.get(0) != null)
305 return iterators.get(0);
306 else
307 return new ConcatLineIterators(iterators);
308 }
309
310 private static class ConcatLineIterators implements BrailleTranslator.LineIterator {
311
312 final List<BrailleTranslator.LineIterator> iterators;
313 BrailleTranslator.LineIterator current;
314 int currentIndex = 0;
315
316 ConcatLineIterators(List<BrailleTranslator.LineIterator> iterators) {
317 this.iterators = iterators;
318 currentIndex = -1;
319 current = null;
320 computeCurrent();
321 }
322
323 void computeCurrent() {
324 while (current == null || !current.hasNext())
325 if (currentIndex + 1 < iterators.size())
326 current = iterators.get(++currentIndex);
327 else {
328 current = null;
329 break; }
330 }
331
332 public String nextTranslatedRow(int limit, boolean force, boolean wholeWordsOnly) {
333 String row = "";
334 BrailleTranslator.LineIterator prev = null;
335 while (limit > row.length()) {
336 if (current == null || current == prev) break;
337 String s = current.nextTranslatedRow(limit - row.length(), force, wholeWordsOnly);
338 if (s.isEmpty() && current.hasNext())
339 break;
340 else
341 force = false;
342 row += s;
343 prev = current;
344 computeCurrent(); }
345 return row;
346 }
347
348 public String getTranslatedRemainder() {
349 String remainder = "";
350 if (current == null) return remainder;
351 for (int i = currentIndex; i < iterators.size(); i++)
352 if (iterators.get(i) != null)
353 remainder += iterators.get(i).getTranslatedRemainder();
354 return remainder;
355 }
356
357 public int countRemaining() {
358 int remaining = 0;
359 if (current == null) return remaining;
360 for (int i = currentIndex; i < iterators.size(); i++)
361 if (iterators.get(i) != null)
362 remaining += iterators.get(i).countRemaining();
363 return remaining;
364 }
365
366 public boolean hasNext() {
367 computeCurrent();
368 return current != null;
369 }
370
371 public ConcatLineIterators copy() {
372 List<BrailleTranslator.LineIterator> iteratorsCopy = new ArrayList<>(iterators.size() - currentIndex);
373 for (int i = currentIndex; i < iterators.size(); i++)
374 if (iterators.get(i) != null)
375 iteratorsCopy.add((BrailleTranslator.LineIterator)iterators.get(i).copy());
376 return new ConcatLineIterators(iteratorsCopy);
377 }
378
379 public boolean supportsMetric(String metric) {
380 return false;
381 }
382
383 public double getMetric(String metric) {
384 throw new UnsupportedMetricException("Metric not supported: " + metric);
385 }
386 }
387
388 private static final Logger logger = LoggerFactory.getLogger(CompoundBrailleTranslator.class);
389
390 }