1 package org.daisy.pipeline.braille.liblouis.impl;
2
3 import java.text.Normalizer;
4 import java.util.ArrayList;
5 import java.util.Arrays;
6 import java.util.HashMap;
7 import static java.util.Collections.singleton;
8 import java.util.List;
9 import java.util.Locale;
10 import java.util.Map;
11 import java.util.stream.Collectors;
12 import java.util.regex.Matcher;
13 import java.util.regex.Pattern;
14
15 import com.google.common.base.MoreObjects;
16 import com.google.common.base.MoreObjects.ToStringHelper;
17 import com.google.common.base.Splitter;
18 import com.google.common.collect.ImmutableList;
19 import com.google.common.collect.ImmutableMap;
20
21 import static com.google.common.collect.Iterables.any;
22 import static com.google.common.collect.Iterables.size;
23 import static com.google.common.collect.Iterables.toArray;
24
25 import cz.vutbr.web.css.CSSProperty;
26 import cz.vutbr.web.css.CSSProperty.FontStyle;
27 import cz.vutbr.web.css.CSSProperty.FontWeight;
28 import cz.vutbr.web.css.CSSProperty.TextDecoration;
29 import cz.vutbr.web.css.Term;
30 import cz.vutbr.web.css.TermIdent;
31 import cz.vutbr.web.css.TermInteger;
32 import cz.vutbr.web.css.TermList;
33
34 import org.daisy.braille.css.BrailleCSSProperty.BrailleCharset;
35 import org.daisy.braille.css.BrailleCSSProperty.Hyphens;
36 import org.daisy.braille.css.BrailleCSSProperty.LetterSpacing;
37 import org.daisy.braille.css.BrailleCSSProperty.TextTransform;
38 import org.daisy.braille.css.BrailleCSSProperty.WhiteSpace;
39 import org.daisy.braille.css.PropertyValue;
40 import org.daisy.braille.css.SimpleInlineStyle;
41
42 import org.daisy.pipeline.braille.common.AbstractBrailleTranslator;
43 import org.daisy.pipeline.braille.common.AbstractBrailleTranslator.util.DefaultLineBreaker;
44 import org.daisy.pipeline.braille.common.AbstractHyphenator.util.NoHyphenator;
45 import org.daisy.pipeline.braille.common.AbstractTransformProvider;
46 import org.daisy.pipeline.braille.common.AbstractTransformProvider.util.Iterables;
47 import org.daisy.pipeline.braille.common.AbstractTransformProvider.util.Function;
48 import static org.daisy.pipeline.braille.common.AbstractTransformProvider.util.Iterables.memoize;
49 import static org.daisy.pipeline.braille.common.AbstractTransformProvider.util.Iterables.transform;
50 import static org.daisy.pipeline.braille.common.AbstractTransformProvider.util.logCreate;
51 import static org.daisy.pipeline.braille.common.AbstractTransformProvider.util.logSelect;
52 import org.daisy.pipeline.braille.common.BrailleCode;
53 import org.daisy.pipeline.braille.common.BrailleTranslator;
54 import org.daisy.pipeline.braille.common.BrailleTranslatorProvider;
55 import org.daisy.pipeline.braille.common.CompoundBrailleTranslator;
56 import org.daisy.pipeline.braille.common.Hyphenator;
57 import org.daisy.pipeline.braille.common.Hyphenator.NonStandardHyphenationException;
58 import org.daisy.pipeline.braille.common.Query;
59 import org.daisy.pipeline.braille.common.Query.Feature;
60 import org.daisy.pipeline.braille.common.Query.MutableQuery;
61 import static org.daisy.pipeline.braille.common.Query.util.mutableQuery;
62 import org.daisy.pipeline.braille.common.TransformationException;
63 import org.daisy.pipeline.braille.common.TransformProvider;
64 import org.daisy.pipeline.braille.common.UnityBrailleTranslator;
65 import static org.daisy.pipeline.braille.common.util.Strings.extractHyphens;
66 import static org.daisy.pipeline.braille.common.util.Strings.insertHyphens;
67 import static org.daisy.pipeline.braille.common.util.Strings.join;
68 import static org.daisy.pipeline.braille.common.util.Strings.splitInclDelimiter;
69 import static org.daisy.pipeline.braille.common.util.Tuple2;
70 import org.daisy.pipeline.braille.css.CSSStyledText;
71 import org.daisy.pipeline.braille.css.TextStyleParser;
72 import org.daisy.pipeline.braille.liblouis.LiblouisTable;
73 import org.daisy.pipeline.braille.liblouis.LiblouisTranslator;
74 import org.daisy.pipeline.braille.liblouis.impl.LiblouisTableJnaImplProvider.LiblouisTableJnaImpl;
75 import org.daisy.pipeline.braille.liblouis.pef.LiblouisDisplayTableBrailleConverter;
76
77 import org.liblouis.DisplayException;
78 import org.liblouis.DisplayTable;
79 import org.liblouis.TableInfo;
80 import org.liblouis.TranslationException;
81 import org.liblouis.TranslationResult;
82 import org.liblouis.Translator;
83 import org.liblouis.Typeform;
84
85 import org.osgi.service.component.annotations.Component;
86 import org.osgi.service.component.annotations.Reference;
87 import org.osgi.service.component.annotations.ReferenceCardinality;
88 import org.osgi.service.component.annotations.ReferencePolicy;
89
90 import static org.slf4j.helpers.NOPLogger.NOP_LOGGER;
91 import org.slf4j.Logger;
92 import org.slf4j.LoggerFactory;
93
94
95
96
97 @Component(
98 name = "org.daisy.pipeline.braille.liblouis.impl.LiblouisTranslatorJnaImplProvider",
99 service = {
100 LiblouisTranslator.Provider.class,
101 BrailleTranslatorProvider.class,
102 TransformProvider.class
103 }
104 )
105 public class LiblouisTranslatorJnaImplProvider extends AbstractTransformProvider<LiblouisTranslator> implements LiblouisTranslator.Provider {
106
107 private final static char SHY = '\u00AD';
108 private final static char ZWSP = '\u200B';
109 private final static char NBSP = '\u00A0';
110 private final static char LS = '\u2028';
111 private final static char RS = '\u001E';
112 private final static char US = '\u001F';
113 private final static Splitter SEGMENT_SPLITTER = Splitter.on(RS);
114 private final static Pattern ON_NBSP_SPLITTER = Pattern.compile("[" + SHY + ZWSP + "]*" + NBSP + "[" + SHY + ZWSP + NBSP + "]*");
115 private final static Pattern ON_SPACE_SPLITTER = Pattern.compile("[" + SHY + ZWSP + "]*[\\x20\t\\n\\r\\u2800" + NBSP + "][" + SHY + ZWSP + "\\x20\t\\n\\r\\u2800" + NBSP+ "]*");
116 private final static Pattern LINE_SPLITTER = Pattern.compile("[" + SHY + ZWSP + "]*[\\n\\r][" + SHY + ZWSP + "\\n\\r]*");
117 private final static Pattern WORD_SPLITTER = Pattern.compile("[\\x20\t\\n\\r\\u2800" + NBSP + "]+");
118
119 private LiblouisTableJnaImplProvider tableProvider;
120
121 @Reference(
122 name = "LiblouisTableJnaImplProvider",
123 unbind = "-",
124 service = LiblouisTableJnaImplProvider.class,
125 cardinality = ReferenceCardinality.MANDATORY,
126 policy = ReferencePolicy.STATIC
127 )
128 protected void bindLiblouisTableJnaImplProvider(LiblouisTableJnaImplProvider provider) {
129 tableProvider = provider;
130 logger.debug("Registering Liblouis table provider: " + provider);
131 }
132
133 protected void unbindLiblouisTableJnaImplProvider(LiblouisTableJnaImplProvider provider) {
134 tableProvider = null;
135 }
136
137 private final static Iterable<LiblouisTranslator> empty
138 = Iterables.<LiblouisTranslator>empty();
139
140 private final static List<String> supportedInput = ImmutableList.of("text-css");
141
142 @Override
143 protected final Iterable<LiblouisTranslator> _get(Query query) {
144 MutableQuery q = mutableQuery(query);
145 if (q.containsKey("braille-code")) {
146
147
148 String code = q.removeOnly("braille-code").getValue().get();
149 if (!q.isEmpty())
150 return empty;
151 try {
152 return get(BrailleCode.parse(code));
153 } catch (IllegalArgumentException e) {
154 return empty;
155 }
156 }
157 for (Feature f : q.removeAll("input"))
158 if (!supportedInput.contains(f.getValue().get()))
159 return empty;
160 if (q.containsKey("output")) {
161 String v = q.removeOnly("output").getValue().get();
162 if ("braille".equals(v)) {}
163 else
164 return empty; }
165 if (q.containsKey("translator"))
166 if (!"liblouis".equals(q.removeOnly("translator").getValue().get()))
167 return empty;
168 String table = null;
169 if (q.containsKey("liblouis-table"))
170 table = q.removeOnly("liblouis-table").getValue().get();
171 if (q.containsKey("table"))
172 if (table != null) {
173 logger.warn("A query with both 'table' and 'liblouis-table' never matches anything");
174 return empty; }
175 else
176 table = q.removeOnly("table").getValue().get();
177 String v = null;
178 if (q.containsKey("handle-non-standard-hyphenation"))
179 v = q.removeOnly("handle-non-standard-hyphenation").getValue().get();
180 else
181 v = "ignore";
182 final int handleNonStandardHyphenation = v.equalsIgnoreCase("fail") ?
183 LiblouisTranslatorImpl.NON_STANDARD_HYPH_FAIL : v.equalsIgnoreCase("defer") ?
184 LiblouisTranslatorImpl.NON_STANDARD_HYPH_DEFER :
185 LiblouisTranslatorImpl.NON_STANDARD_HYPH_IGNORE;
186 if (q.containsKey("include-braille-code-in-language"))
187 v = q.removeOnly("include-braille-code-in-language").getValue().orElse("true");
188 else
189 v = "false";
190 boolean includeBrailleCodeInLanguage = v.equalsIgnoreCase("true");
191 if (table != null)
192 q.add("table", table);
193 q.add("white-space");
194 Iterable<LiblouisTranslator> translators = memoize(
195 getSimpleTranslator(
196 q.asImmutable(),
197 handleNonStandardHyphenation,
198 includeBrailleCodeInLanguage));
199 if (translators.apply(NOP_LOGGER).iterator().hasNext()) {
200
201
202 LiblouisTableJnaImpl t = tableProvider.withContext(NOP_LOGGER).get(q).iterator().next();
203 BrailleTranslator unityTranslator = new UnityBrailleTranslator(
204 t.usesCustomDisplayTable()
205 ? new LiblouisDisplayTableBrailleConverter(t.getDisplayTable())
206 : null,
207 false);
208 return Iterables.transform(
209 translators,
210 new Function<LiblouisTranslator,LiblouisTranslator>() {
211 public LiblouisTranslator _apply(LiblouisTranslator t) {
212 return __apply(logCreate(new HandleTextTransformNone(t, unityTranslator))); }});
213 } else
214 return translators;
215 }
216
217 private Iterable<LiblouisTranslator> getSimpleTranslator(Query query,
218 int handleNonStandardHyphenation,
219 boolean includeBrailleCodeInLanguage) {
220 return transform(
221 logSelect(query, tableProvider),
222 new Function<LiblouisTableJnaImpl,LiblouisTranslator>() {
223 public LiblouisTranslator _apply(LiblouisTableJnaImpl table) {
224 return __apply(
225 logCreate((LiblouisTranslator)new LiblouisTranslatorImpl(
226 table,
227 null,
228 handleNonStandardHyphenation,
229 includeBrailleCodeInLanguage)));
230 }
231 }
232 );
233 }
234
235
236
237
238
239
240 private final Map<BrailleCode,String> brailleCodeCache = new HashMap<>();
241
242 private Iterable<LiblouisTranslator> get(BrailleCode code) {
243 String id = brailleCodeCache.get(code);
244 if (id == null)
245 throw new IllegalStateException();
246 return Iterables.of(fromId(id));
247 }
248
249 @Override
250 public ToStringHelper toStringHelper() {
251 return MoreObjects.toStringHelper("LiblouisTranslatorJnaImplProvider");
252 }
253
254 private final static TextStyleParser cssParser = TextStyleParser.getInstance();
255 private final static PropertyValue TEXT_TRANSFORM_NONE
256 = cssParser.parse("text-transform: none").get("text-transform");
257 private final static PropertyValue BRAILLE_CHARSET_CUSTOM
258 = cssParser.parse("braille-charset: custom").get("braille-charset");
259
260 class LiblouisTranslatorImpl extends AbstractBrailleTranslator implements LiblouisTranslator {
261
262 private final LiblouisTableJnaImpl table;
263 protected final Translator translator;
264 private final DisplayTable displayTable;
265 private Hyphenator hyphenator;
266 protected FullHyphenator fullHyphenator;
267 private Hyphenator.LineBreaker lineBreaker;
268 private final Map<String,Typeform> supportedTypeforms;
269 private Map<String,String> infoMap = null;
270 private BrailleCode brailleCode = null;
271
272
273
274
275
276
277
278
279
280 private final int handleNonStandardHyphenation;
281 private final boolean includeBrailleCodeInLanguage;
282 private final Normalizer.Form unicodeNormalization;
283
284 public final static int NON_STANDARD_HYPH_IGNORE = 0;
285 public final static int NON_STANDARD_HYPH_FAIL = 1;
286 public final static int NON_STANDARD_HYPH_DEFER = 2;
287
288
289
290
291
292
293
294
295
296
297
298
299 LiblouisTranslatorImpl(LiblouisTableJnaImpl table,
300 Hyphenator hyphenator,
301 int handleNonStandardHyphenation,
302 boolean includeBrailleCodeInLanguage) {
303 super(hyphenator, null);
304 this.table = table;
305 this.translator = table.getTranslator();
306 this.displayTable = table.getDisplayTable();
307 this.handleNonStandardHyphenation = handleNonStandardHyphenation;
308 this.includeBrailleCodeInLanguage = includeBrailleCodeInLanguage;
309 this.supportedTypeforms
310 = translator.getSupportedTypeforms().stream().collect(Collectors.toMap(Typeform::getName, e -> e));
311 this.unicodeNormalization = table.getUnicodeNormalizationForm();
312 this.hyphenator = hyphenator;
313 if (hyphenator == null)
314 fullHyphenator = compoundWordHyphenator;
315 else {
316 try {
317 fullHyphenator = new HyphenatorAsFullHyphenator(hyphenator); }
318 catch (UnsupportedOperationException e) {}
319 try {
320 lineBreaker = hyphenator.asLineBreaker(); }
321 catch (UnsupportedOperationException e) {}}
322 }
323
324 private LiblouisTranslatorImpl(LiblouisTranslatorImpl from, Hyphenator hyphenator) {
325 super(from);
326 this.table = from.table;
327 this.translator = from.translator;
328 this.displayTable = from.displayTable;
329 this.handleNonStandardHyphenation = from.handleNonStandardHyphenation;
330 this.includeBrailleCodeInLanguage = from.includeBrailleCodeInLanguage;
331 this.supportedTypeforms = from.supportedTypeforms;
332 this.unicodeNormalization = from.unicodeNormalization;
333 this.hyphenator = hyphenator;
334 if (hyphenator == null)
335 fullHyphenator = compoundWordHyphenator;
336 else {
337 try {
338 fullHyphenator = new HyphenatorAsFullHyphenator(hyphenator); }
339 catch (UnsupportedOperationException e) {}
340 try {
341 lineBreaker = hyphenator.asLineBreaker(); }
342 catch (UnsupportedOperationException e) {}}
343 }
344
345
346 @Override
347 public LiblouisTable asLiblouisTable() {
348 return table;
349 }
350
351 private BrailleCode getBrailleCode() {
352 if (brailleCode == null) {
353
354
355
356
357 Map<String,String> info = getInfo();
358 String indexName = info.get("index-name");
359 if (indexName == null)
360 throw new IllegalStateException();
361
362 if ("International Phonetic Alphabet".equals(indexName))
363 brailleCode = new BrailleCode("IPA", BrailleCode.Grade.NO_GRADE, BrailleCode.Specialization.PHONETIC);
364 else if ("Russian, for program sources".equals(indexName))
365
366 brailleCode = new BrailleCode("Russian", BrailleCode.Grade.GRADE0, BrailleCode.Specialization.COMP6);
367 else {
368 if (indexName.startsWith("English, unified"))
369 indexName = indexName.replace("English, unified", "UEB");
370 else if ("Serbian, Cyrillic".equals(indexName))
371
372 indexName = "Serbian";
373 else if ("Mandarin, Taiwan".equals(indexName))
374 indexName = "Taiwanese";
375 else if (indexName.startsWith("Mandarin, mainland China"))
376 indexName = indexName.replace("Mandarin, mainland China", "Chinese");
377 BrailleCode.Grade grade = BrailleCode.Grade.NO_GRADE; {
378 String g = info.get("grade");
379 if (g != null)
380 try {
381 grade = BrailleCode.Grade.valueOf("GRADE" + g);
382 } catch (IllegalArgumentException e) {
383 throw new IllegalStateException(
384 "grade not supported by eBraille braille code identifier: " + g, e);
385 }
386 }
387 BrailleCode.Specialization specialization = null; {
388 String t = info.get("type");
389 if (t == null);
390 else if ("computer".equals(t)) {
391 String d = info.get("dots");
392 if ("8".equals(d))
393 specialization = BrailleCode.Specialization.COMP8;
394 else if ("6".equals(d))
395 specialization = BrailleCode.Specialization.COMP6;
396 else
397 throw new IllegalStateException();
398 }
399 }
400 List<String> system = new ArrayList<>();
401 for (String part : indexName.split(", ")) {
402 if (grade != BrailleCode.Grade.NO_GRADE
403 && ("uncontracted".equals(part) || "contracted".equals(part) || "partially contracted".equals(part)
404 || part.startsWith("grade ")))
405 continue;
406 if ((specialization == BrailleCode.Specialization.COMP6
407 || specialization == BrailleCode.Specialization.COMP8)
408 && "computer".equals(part))
409 continue;
410 if (specialization == BrailleCode.Specialization.COMP6 && "6-dot".equals(part))
411 continue;
412 if (specialization == BrailleCode.Specialization.COMP8 && "8-dot".equals(part))
413 continue;
414 system.add(part.replace(" ", "-"));
415 }
416 brailleCode = new BrailleCode(join(system, "-"), grade, specialization);
417 }
418 LiblouisTranslatorJnaImplProvider.this.rememberId(this);
419
420 brailleCodeCache.put(brailleCode, getIdentifier());
421 }
422 return brailleCode;
423 }
424
425 @Override
426 public Map<String,String> getInfo() {
427 if (infoMap == null) {
428 TableInfo info = table.getInfo();
429 ImmutableMap.Builder<String,String> m = new ImmutableMap.Builder<>();
430 if (info != null) {
431 String type = info.get("type");
432 if (type == null)
433 type = "literary";
434 m.put("type", type);
435 String dots = info.get("dots");
436 if (dots == null)
437 dots = type.equals("computer") ? "8" : "6";
438 m.put("dots", dots);
439 for (String k : new String[]{"display-name",
440 "index-name",
441 "contraction",
442 "grade"}) {
443 String v = info.get(k);
444 if (v != null)
445 m.put(k, v);
446 }
447 }
448 infoMap = m.build();
449 }
450 return infoMap;
451 }
452
453 @Override
454 public LiblouisTranslatorImpl _withHyphenator(Hyphenator hyphenator) {
455 if (hyphenator == this.hyphenator)
456 return this;
457 LiblouisTranslatorImpl t = new LiblouisTranslatorImpl(this, hyphenator);
458 LiblouisTranslatorJnaImplProvider.this.rememberId(t);
459 return t;
460 }
461
462 private FromTypeformedTextToBraille fromTypeformedTextToBraille;
463
464 @Override
465 public FromTypeformedTextToBraille fromTypeformedTextToBraille() {
466 if (fromTypeformedTextToBraille == null)
467 fromTypeformedTextToBraille = new FromTypeformedTextToBraille() {
468 public String[] transform(String[] text, String[] emphClasses) {
469 Typeform[] typeform = new Typeform[emphClasses.length];
470 for (int i = 0; i < typeform.length; i++) {
471 typeform[i] = supportedTypeforms.get(emphClasses[i]);
472 if (typeform[i] == null)
473 logger.warn("emphclass 'italic' not defined in table {}", translator.getTable());
474 }
475 return LiblouisTranslatorImpl.this.transform(text, typeform);
476 }
477 @Override
478 public String toString() {
479 return LiblouisTranslatorImpl.this.toString();
480 }
481 };
482 return fromTypeformedTextToBraille;
483 }
484
485 private FromStyledTextToBraille fromStyledTextToBraille;
486
487
488
489
490
491 @Override
492 public FromStyledTextToBraille fromStyledTextToBraille() {
493 if (fromStyledTextToBraille == null)
494 fromStyledTextToBraille = new FromStyledTextToBraille() {
495 public java.lang.Iterable<CSSStyledText> transform(java.lang.Iterable<CSSStyledText> styledText, int from, int to)
496 throws TransformationException {
497 try {
498 List<CSSStyledText> result = LiblouisTranslatorImpl.this.transform(styledText, false, false);
499 if (to < 0) to = result.size();
500 if (from > 0 || to < result.size())
501 return result.subList(from, to);
502 else
503 return result;
504 } catch (NonStandardHyphenationException e) {
505 throw new TransformationException(e);
506 }
507 }
508 @Override
509 public String toString() {
510 return LiblouisTranslatorImpl.this.toString();
511 }
512 };
513 return fromStyledTextToBraille;
514 }
515
516 private LineBreakingFromStyledText lineBreakingFromStyledText;
517
518 @Override
519 public LineBreakingFromStyledText lineBreakingFromStyledText() {
520 if (lineBreakingFromStyledText == null)
521 lineBreakingFromStyledText = new LineBreaker(
522 new FromStyledTextToBraille() {
523 public java.lang.Iterable<CSSStyledText> transform(java.lang.Iterable<CSSStyledText> styledText, int from, int to) {
524 List<CSSStyledText> result = LiblouisTranslatorImpl.this.transform(styledText, true, true);
525 if (to < 0) to = result.size();
526 if (from > 0 || to < result.size())
527 return result.subList(from, to);
528 else
529 return result;
530 }
531 }) {
532 @Override
533 public String toString() {
534 return LiblouisTranslatorImpl.this.toString();
535 }
536 };
537 return lineBreakingFromStyledText;
538 }
539
540 class LineBreaker extends DefaultLineBreaker {
541
542 final FromStyledTextToBraille fullTranslator;
543
544 protected LineBreaker(FromStyledTextToBraille fullTranslator) {
545
546 super(displayTable.encode('\u2800'),
547 displayTable.encode('\u2824'),
548 new LiblouisDisplayTableBrailleConverter(displayTable),
549 logger);
550 this.fullTranslator = fullTranslator;
551 }
552
553 protected BrailleStream translateAndHyphenate(java.lang.Iterable<CSSStyledText> styledText, int from, int to) {
554 java.lang.Iterable<CSSStyledText> braille;
555 try {
556 braille = fullTranslator.transform(styledText); }
557 catch (NonStandardHyphenationException e) {
558 return new BrailleStreamImpl(styledText,
559 from,
560 to); }
561
562 List<String> brailleWithPreservedWS = new ArrayList<>(); {
563 for (CSSStyledText b : braille) {
564 String t = b.getText();
565
566
567 SimpleInlineStyle s = b.getStyle();
568 if (s != null) {
569 Hyphens h = s.getProperty("hyphens");
570 if (h == Hyphens.NONE || h == Hyphens.MANUAL) {
571 if (h == Hyphens.NONE)
572 t = t.replaceAll("[\u00AD\u200B]","");
573 s.removeProperty("hyphens"); }
574 WhiteSpace ws = s.getProperty("white-space");
575 if (ws != null) {
576 if (ws == WhiteSpace.PRE_WRAP)
577 t = t.replaceAll("[\\x20\t\\u2800]+", "$0"+ZWSP)
578 .replaceAll("[\\x20\t\\u2800]", ""+NBSP);
579 if (ws == WhiteSpace.PRE_WRAP || ws == WhiteSpace.PRE_LINE)
580 t = t.replaceAll("[\\n\\r]", ""+LS);
581 s.removeProperty("white-space"); }
582 TextTransform tt = s.getProperty("text-transform");
583 if (tt == TextTransform.NONE)
584 s.removeProperty("text-transform");
585 s.removeProperty("braille-charset");
586 for (String prop : s.getPropertyNames())
587 logger.warn("{} not supported", s.get(prop));
588 }
589 brailleWithPreservedWS.add(t);
590 }
591 }
592 StringBuilder joined = new StringBuilder();
593 int fromChar = 0;
594 int toChar = to >= 0 ? 0 : -1;
595 for (String s : brailleWithPreservedWS) {
596 joined.append(s);
597 if (--from == 0)
598 fromChar = joined.length();
599 if (--to == 0)
600 toChar = joined.length();
601 }
602 return new FullyHyphenatedAndTranslatedString(joined.toString(), fromChar, toChar);
603 }
604
605 class BrailleStreamImpl implements BrailleStream {
606
607 final Locale[] languages;
608
609
610
611
612 final Typeform[] typeform;
613 final boolean[] hyphenate;
614 final boolean[] preserveLines;
615 final boolean[] preserveSpace;
616 final int[] letterSpacing;
617
618
619
620 final String[] textWithWs;
621
622
623
624 final boolean[] pre;
625
626
627 final int[] textWithWsMapping;
628
629
630
631 String joinedText;
632
633
634 int[] joinedTextMapping;
635
636
637 byte[] manualHyphens;
638
639
640 String joinedBraille;
641
642
643 int[] characterIndicesInBraille;
644
645
646
647 int[] interCharacterIndicesInBraille;
648
649
650 int curPos = -1;
651 int curPosInBraille = -1;
652 int endPos = -1;
653 int endPosInBraille = -1;
654 final int to;
655
656 BrailleStreamImpl(java.lang.Iterable<CSSStyledText> styledText,
657 int from,
658 int to) {
659
660
661 int size = size(styledText);
662 if (to < 0) to = size;
663 this.to = to;
664
665
666 String[] text = new String[size];
667 SimpleInlineStyle[] styles = new SimpleInlineStyle[size];
668 languages = new Locale[size]; {
669 int i = 0;
670 for (CSSStyledText t : styledText) {
671 text[i] = t.getText();
672 styles[i] = t.getStyle();
673 if (styles[i] != null)
674 styles[i] = (SimpleInlineStyle)styles[i].clone();
675 languages[i] = t.getLanguage();
676 i++; }}
677
678
679 if (unicodeNormalization != null)
680 for (int k = 0; k < text.length; k++)
681 text[k] = Normalizer.normalize(text[k], unicodeNormalization);
682
683 {
684 typeform = new Typeform[size];
685 hyphenate = new boolean[size];
686 preserveLines = new boolean[size];
687 preserveSpace = new boolean[size];
688 letterSpacing = new int[size];
689 for (int i = 0; i < size; i++) {
690 typeform[i] = Typeform.PLAIN_TEXT;
691 hyphenate[i] = false;
692 preserveLines[i] = preserveSpace[i] = false;
693 letterSpacing[i] = 0;
694 SimpleInlineStyle style = styles[i];
695 if (style != null) {
696 CSSProperty val = style.getProperty("white-space");
697 if (val != null) {
698 if (val == WhiteSpace.PRE_WRAP)
699 preserveLines[i] = preserveSpace[i] = true;
700 else if (val == WhiteSpace.PRE_LINE)
701 preserveLines[i] = true;
702 style.removeProperty("white-space"); }
703 val = style.getProperty("text-transform");
704 if (val != null) {
705 if (val == TextTransform.NONE) {
706
707
708
709
710
711
712 val = style.getProperty("braille-charset");
713 if (val != null) {
714 if (val == BrailleCharset.CUSTOM)
715
716 text[i] = displayTable.decode(text[i]);
717 style.removeProperty("braille-charset"); }
718 style.removeProperty("text-transform");
719 continue; }
720 else if (val == TextTransform.AUTO) {}
721 else if (val == TextTransform.list_values) {
722 TermList values = style.getValue(TermList.class, "text-transform");
723 text[i] = textFromTextTransform(text[i], values);
724 typeform[i] = typeform[i].add(typeformFromTextTransform(values, translator, supportedTypeforms)); }
725 style.removeProperty("text-transform"); }
726 val = style.getProperty("hyphens");
727 if (val != null) {
728 if (val == Hyphens.AUTO)
729 hyphenate[i] = true;
730 else if (val == Hyphens.NONE)
731 text[i] = extractHyphens(text[i], false, SHY, ZWSP)._1;
732 style.removeProperty("hyphens"); }
733 val = style.getProperty("letter-spacing");
734 if (val != null) {
735 if (val == LetterSpacing.length) {
736 letterSpacing[i] = style.getValue(TermInteger.class, "letter-spacing").getIntValue();
737 if (letterSpacing[i] < 0) {
738 logger.warn("{} not supported, must be non-negative", val);
739 letterSpacing[i] = 0; }}
740 style.removeProperty("letter-spacing"); }
741 typeform[i] = typeform[i].add(typeformFromInlineCSS(style, translator, supportedTypeforms));
742 for (String prop : style.getPropertyNames())
743 logger.warn("{} not supported", style.get(prop)); }}
744 }
745 {
746 List<String> l1 = new ArrayList<String>();
747 List<Boolean> l2 = new ArrayList<Boolean>();
748 List<Integer> l3 = new ArrayList<Integer>();
749 for (int i = 0; i < text.length; i++) {
750 String t = text[i];
751 if (t.isEmpty()) {
752 l1.add(t);
753 l2.add(false);
754 l3.add(i); }
755 else {
756 Pattern ws;
757 if (preserveSpace[i])
758 ws = ON_SPACE_SPLITTER;
759 else if (preserveLines[i])
760 ws = LINE_SPLITTER;
761 else
762 ws = ON_NBSP_SPLITTER;
763 boolean p = false;
764 for (String s : splitInclDelimiter(t, ws)) {
765 if (!s.isEmpty()) {
766 l1.add(s);
767 l2.add(p);
768 l3.add(i); }
769 p = !p; }}}
770 int len = l1.size();
771 textWithWs = new String[len];
772 pre = new boolean[len];
773 textWithWsMapping = new int[len];
774 for (int i = 0; i < len; i++) {
775 textWithWs[i] = l1.get(i);
776 pre[i] = l2.get(i);
777 textWithWsMapping[i] = l3.get(i); }
778 }
779 {
780 String[] textWithWsReplaced = new String[textWithWs.length];
781 for (int i = 0; i < textWithWs.length; i++)
782 textWithWsReplaced[i] = pre[i] ? ""+NBSP : textWithWs[i];
783 Tuple2<String,byte[]> t = extractHyphens(join(textWithWsReplaced, RS), true, SHY, ZWSP);
784 manualHyphens = t._2;
785 String[] nohyph = toArray(SEGMENT_SPLITTER.split(t._1), String.class);
786 joinedTextMapping = new int[lengthByCodePoints(join(nohyph))];
787 int i = 0;
788 int j = 0;
789 for (String s : nohyph) {
790 int l = lengthByCodePoints(s);
791 for (int k = 0; k < l; k++)
792 joinedTextMapping[i++] = j;
793 j++; }
794 t = extractHyphens(manualHyphens, t._1, true, null, null, null, RS);
795 joinedText = t._1;
796 }
797 {
798 int fromChar = -1;
799 int toChar = -1;
800 for (int i = 0; i < joinedTextMapping.length; i++) {
801 if (fromChar < 0 || (toChar < 0 && to >= 0)) {
802 int indexInText = textWithWsMapping[joinedTextMapping[i]];
803 if (fromChar < 0 && indexInText >= from)
804 fromChar = i;
805 if (toChar < 0 && indexInText >= to)
806 toChar = i;
807 } else
808 break;
809 }
810 if (toChar < 0) toChar = joinedTextMapping.length;
811 this.curPos = fromChar;
812 this.endPos = toChar;
813 }
814 }
815
816 public String next(final int limit, final boolean force, boolean allowHyphens) {
817 String next = "";
818 if (limit > 0) {
819 int available = limit;
820 segments: while (true) {
821 if (curPos == endPos)
822 break;
823 if (joinedBraille == null)
824 updateBraille();
825 int curSegment = joinedTextMapping[curPos];
826 int curSegmentEnd; {
827 int i = curPos;
828 for (; i < endPos; i++)
829 if (joinedTextMapping[i] > curSegment)
830 break;
831 curSegmentEnd = i; }
832 int curSegmentEndInBraille = positionInBraille(curSegmentEnd);
833 if (curSegmentEndInBraille == curPosInBraille)
834 continue segments;
835 String segment = substringByCodePoints(joinedText, curPos, curSegmentEnd);
836 String segmentInBraille = joinedBraille.substring(curPosInBraille, curSegmentEndInBraille);
837 byte[] segmentManualHyphens = manualHyphens != null
838 ? Arrays.copyOfRange(manualHyphens, curPos, curSegmentEnd - 1)
839 : null;
840
841
842 if (pre[curSegment]) {
843 Matcher m = Pattern.compile("\\xA0([\\xAD\\u200B]*)").matcher(segmentInBraille);
844 if (m.matches()) {
845 String restoredSpace = segment.replaceAll("[\\x20\t\\u2800]", ""+NBSP)
846 .replaceAll("[\\n\\r]", ""+LS) + m.group(1);
847 next += restoredSpace;
848 available -= lengthByCodePoints(restoredSpace);
849 curPos = curSegmentEnd;
850 curPosInBraille = curSegmentEndInBraille;
851 continue segments; }}
852
853
854
855 if (segmentInBraille.length() <= available) {
856 segmentInBraille = addLetterSpacing(segment, segmentInBraille, curPos, curPosInBraille,
857 letterSpacing[textWithWsMapping[curSegment]]);
858 next += segmentInBraille;
859 available -= segmentInBraille.length();
860 curPos = curSegmentEnd;
861 curPosInBraille = curSegmentEndInBraille;
862 continue segments; }
863
864
865 Locale language = languages[textWithWsMapping[curSegment]];
866 if (!hyphenate[textWithWsMapping[curSegment]]) {
867 segmentInBraille = addHyphensAndLetterSpacing(compoundWordHyphenator,
868 segment, segmentInBraille, curPos, curPosInBraille,
869 segmentManualHyphens, language,
870 letterSpacing[textWithWsMapping[curSegment]]);
871 next += segmentInBraille;
872 available -= segmentInBraille.length();
873 curPos = curSegmentEnd;
874 curPosInBraille = curSegmentEndInBraille;
875 continue segments; }
876
877
878 if (fullHyphenator != null) {
879 if (fullHyphenator == compoundWordHyphenator)
880 logger.warn("hyphens: auto not supported");
881 try {
882 segmentInBraille = addHyphensAndLetterSpacing(fullHyphenator, segment, segmentInBraille, curPos, curPosInBraille,
883 segmentManualHyphens, language,
884 letterSpacing[textWithWsMapping[curSegment]]);
885 next += segmentInBraille;
886 available -= segmentInBraille.length();
887 curPos = curSegmentEnd;
888 curPosInBraille = curSegmentEndInBraille;
889 continue segments; }
890 catch (NonStandardHyphenationException e) {}}
891
892
893 Matcher m = WORD_SPLITTER.matcher(segment);
894 int segmentStart = curPos;
895 boolean foundSpace;
896 while ((foundSpace = m.find()) || curPos < curSegmentEnd) {
897 int wordEnd = foundSpace ? segmentStart + m.start() : curSegmentEnd;
898 if (wordEnd > curPos) {
899 int wordEndInBraille = positionInBraille(wordEnd);
900 if (wordEndInBraille > curPosInBraille) {
901 String word = substringByCodePoints(joinedText, curPos, wordEnd);
902 String wordInBraille = joinedBraille.substring(curPosInBraille, wordEndInBraille);
903 byte[] wordManualHyphens = manualHyphens != null
904 ? Arrays.copyOfRange(manualHyphens, curPos, wordEnd - 1)
905 : null;
906
907
908 if (wordInBraille.length() <= available) {
909 next += wordInBraille;
910 available -= wordInBraille.length();
911 curPos = wordEnd;
912 curPosInBraille = wordEndInBraille; }
913 else {
914
915
916 try {
917 if (fullHyphenator == null) throw new NonStandardHyphenationException();
918 wordInBraille = addHyphensAndLetterSpacing(fullHyphenator, word, wordInBraille, curPos, curPosInBraille,
919 wordManualHyphens, language,
920 letterSpacing[textWithWsMapping[curSegment]]);
921 next += wordInBraille;
922 available -= wordInBraille.length();
923 curPos = wordEnd;
924 curPosInBraille = wordEndInBraille; }
925 catch (NonStandardHyphenationException ee) {
926
927
928
929
930
931 if (!next.isEmpty())
932 break segments;
933
934
935 if (lineBreaker == null) throw ee;
936 Hyphenator.LineIterator lines = lineBreaker.transform(word, language);
937
938
939 LineBreakSolution bestSolution = null;
940 int left = 1;
941 int right = lengthByCodePoints(word) - 1;
942 int textAvailable = available;
943 if (textAvailable > right)
944 textAvailable = right;
945 if (textAvailable < left)
946 break segments;
947 while (true) {
948 String line = lines.nextLine(textAvailable, force && next.isEmpty(), allowHyphens);
949 String replacementWord = line + lines.remainder();
950 if (updateInput(curPos, wordEnd, replacementWord)) {
951 wordEnd = curPos + lengthByCodePoints(replacementWord);
952 updateBraille(); }
953 int lineEnd = curPos + lengthByCodePoints(line);
954 int lineEndInBraille = positionInBraille(lineEnd);
955 String lineInBraille = joinedBraille.substring(curPosInBraille, lineEndInBraille);
956 lineInBraille = addLetterSpacing(line, lineInBraille, curPos, curPosInBraille,
957 letterSpacing[textWithWsMapping[curSegment]]);
958 int lineInBrailleLength = lineInBraille.length();
959 if (lines.lineHasHyphen()) {
960 lineInBraille += "\u00ad";
961 lineInBrailleLength++; }
962 if (lineInBrailleLength == available) {
963 bestSolution = new LineBreakSolution(); {
964 bestSolution.line = line;
965 bestSolution.replacementWord = replacementWord;
966 bestSolution.lineInBraille = lineInBraille;
967 bestSolution.lineInBrailleLength = lineInBrailleLength; }
968 left = textAvailable + 1;
969 right = textAvailable - 1; }
970 else if (lineInBrailleLength < available) {
971 left = textAvailable + 1;
972 if (bestSolution == null || lineInBrailleLength > bestSolution.lineInBrailleLength) {
973 bestSolution = new LineBreakSolution(); {
974 bestSolution.line = line;
975 bestSolution.replacementWord = replacementWord;
976 bestSolution.lineInBraille = lineInBraille;
977 bestSolution.lineInBrailleLength = lineInBrailleLength; }}}
978 else
979 right = textAvailable - 1;
980 lines.reset();
981 textAvailable = (right + left) / 2;
982 if (textAvailable < left || textAvailable > right) {
983 if (bestSolution != null) {
984 next += bestSolution.lineInBraille;
985 available = 0;
986 if (updateInput(curPos, wordEnd, bestSolution.replacementWord))
987 updateBraille();
988 curPos += lengthByCodePoints(bestSolution.line);
989 curPosInBraille = positionInBraille(curPos); }
990 else if (force && next.isEmpty()) {
991 next = wordInBraille;
992 available = 0;
993 curPos = wordEnd;
994 curPosInBraille = wordEndInBraille; }
995 break segments; } }}}}}
996 if (foundSpace) {
997 int spaceEnd = segmentStart + m.end();
998 int spaceEndInBraille = positionInBraille(spaceEnd);
999 if (spaceEndInBraille > curPosInBraille) {
1000 String spaceInBraille = joinedBraille.substring(curPosInBraille, spaceEndInBraille);
1001 next += spaceInBraille;
1002 available -= spaceInBraille.length();
1003 curPos = spaceEnd;
1004 curPosInBraille = spaceEndInBraille; }}}}
1005 }
1006 if (lastPeek != null && !next.isEmpty() && next.charAt(0) != lastPeek)
1007 throw new IllegalStateException();
1008 lastPeek = null;
1009 return next;
1010 }
1011
1012 public boolean hasNext() {
1013 if (joinedBraille == null)
1014 updateBraille();
1015 boolean hasNextOutput = curPosInBraille < endPosInBraille;
1016 boolean hasNextInput = curPos < endPos;
1017 if (hasNextInput != hasNextOutput)
1018 throw new RuntimeException("coding error");
1019 return hasNextOutput;
1020 }
1021
1022 Character lastPeek = null;
1023
1024 public Character peek() {
1025 if (joinedBraille == null)
1026 updateBraille();
1027 lastPeek = joinedBraille.charAt(curPosInBraille);
1028 return lastPeek;
1029 }
1030
1031
1032
1033 public String remainder() {
1034 if (joinedBraille == null)
1035 updateBraille();
1036 return joinedBraille.substring(curPosInBraille, endPosInBraille);
1037 }
1038
1039
1040
1041 public boolean hasPrecedingSpace() {
1042 if (joinedBraille == null)
1043 updateBraille();
1044 return DefaultLineBreaker.hasPrecedingSpace(joinedBraille, curPosInBraille);
1045 }
1046
1047 @Override
1048 public Object clone() {
1049 try {
1050 BrailleStreamImpl clone = (BrailleStreamImpl)super.clone();
1051 if (joinedTextMapping != null)
1052 clone.joinedTextMapping = joinedTextMapping.clone();
1053 if (manualHyphens != null)
1054 clone.manualHyphens = manualHyphens.clone();
1055 if (characterIndicesInBraille != null)
1056 clone.characterIndicesInBraille = characterIndicesInBraille.clone();
1057 if (interCharacterIndicesInBraille != null)
1058 clone.interCharacterIndicesInBraille = interCharacterIndicesInBraille.clone();
1059 return clone;
1060 } catch (CloneNotSupportedException e) {
1061 throw new InternalError("coding error");
1062 }
1063 }
1064
1065 private int positionInBraille(int pos) {
1066 int posInBraille = curPosInBraille;
1067 if (posInBraille < 0) posInBraille = 0;
1068 for (; posInBraille < joinedBraille.length(); posInBraille++)
1069 if (characterIndicesInBraille[posInBraille] >= pos)
1070 break;
1071 return posInBraille;
1072 }
1073
1074 private String addHyphensAndLetterSpacing(FullHyphenator fullHyphenator,
1075 String segment,
1076 String segmentInBraille,
1077 int curPos,
1078 int curPosInBraille,
1079 byte[] manualHyphens,
1080 Locale language,
1081 int letterSpacing) {
1082 byte[] hyphens = fullHyphenator.hyphenate(
1083
1084 insertHyphens(segment, manualHyphens, true, SHY, ZWSP),
1085 language);
1086
1087 byte[] hyphensAndLetterBoundaries
1088 = (letterSpacing > 0) ? detectLetterBoundaries(hyphens, segment, (byte)4) : hyphens;
1089 if (hyphensAndLetterBoundaries == null && manualHyphens == null)
1090 return segment;
1091 byte[] hyphensAndLetterBoundariesInBraille = new byte[segmentInBraille.length() - 1];
1092 if (hyphensAndLetterBoundaries != null) {
1093 for (int i = 0; i < hyphensAndLetterBoundariesInBraille.length; i++) {
1094 int pos = interCharacterIndicesInBraille[curPosInBraille + i] - 1;
1095 if (pos >= 0)
1096 hyphensAndLetterBoundariesInBraille[i] = hyphensAndLetterBoundaries[pos - curPos];
1097 }
1098 }
1099 String r = insertHyphens(segmentInBraille, hyphensAndLetterBoundariesInBraille, false, SHY, ZWSP, US);
1100 return (letterSpacing > 0) ? applyLetterSpacing(r, letterSpacing) : r;
1101 }
1102
1103 private String addLetterSpacing(String segment,
1104 String segmentInBraille,
1105 int curPos,
1106 int curPosInBraille,
1107 int letterSpacing) {
1108 if (letterSpacing > 0) {
1109
1110 byte[] letterBoundaries = detectLetterBoundaries(null, segment, (byte)1);
1111 byte[] letterBoundariesInBraille = new byte[segmentInBraille.length() - 1];
1112 for (int i = 0; i < letterBoundariesInBraille.length; i++) {
1113 int pos = interCharacterIndicesInBraille[curPosInBraille + i] - 1;
1114 if (pos >= 0)
1115 letterBoundariesInBraille[i] = letterBoundaries[pos - curPos];
1116 }
1117 return applyLetterSpacing(insertHyphens(segmentInBraille, letterBoundariesInBraille, false, US), letterSpacing); }
1118 else
1119 return segmentInBraille;
1120 }
1121
1122 private boolean updateInput(int start, int end, String replacement) {
1123 if (substringByCodePoints(joinedText, start, end).equals(replacement))
1124 return false;
1125 joinedText = substringByCodePoints(joinedText, 0, start) + replacement + substringByCodePoints(joinedText, end);
1126 {
1127 int[] updatedJoinedTextMapping = new int[lengthByCodePoints(joinedText)];
1128 int i = 0;
1129 int j = 0;
1130 while (i < start)
1131 updatedJoinedTextMapping[j++] = joinedTextMapping[i++];
1132 int startSegment = joinedTextMapping[start];
1133 while (i < end)
1134 if (joinedTextMapping[i++] != startSegment)
1135 throw new RuntimeException("Coding error");
1136 while (j < start + lengthByCodePoints(replacement))
1137 updatedJoinedTextMapping[j++] = startSegment;
1138 while (j < updatedJoinedTextMapping.length)
1139 updatedJoinedTextMapping[j++] = joinedTextMapping[i++];
1140 joinedTextMapping = updatedJoinedTextMapping;
1141 }
1142
1143 if (manualHyphens != null) {
1144 byte[] updatedManualHyphens = new byte[lengthByCodePoints(joinedText) - 1];
1145 int i = 0;
1146 int j = 0;
1147 while (i < start)
1148 updatedManualHyphens[j++] = manualHyphens[i++];
1149 while (j < start + lengthByCodePoints(replacement) - 1)
1150 updatedManualHyphens[j++] = 0;
1151 i = end - 1;
1152 while (j < updatedManualHyphens.length)
1153 updatedManualHyphens[j++] = manualHyphens[i++];
1154 manualHyphens = updatedManualHyphens;
1155 }
1156 {
1157 int toChar = -1;
1158 if (to >= 0)
1159 for (int i = 0; i < joinedTextMapping.length; i++)
1160 if (textWithWsMapping[joinedTextMapping[i]] >= to) {
1161 toChar = i;
1162 break; }
1163 this.endPos = toChar > 0 ? toChar : joinedTextMapping.length;
1164 }
1165 return true;
1166 }
1167
1168
1169 private void updateBraille() {
1170 int joinedTextLength = lengthByCodePoints(joinedText);
1171
1172 String partBeforeCurPos = curPosInBraille > 0 ? joinedBraille.substring(0, curPosInBraille): null;
1173 int[] characterIndices = new int[joinedTextLength]; {
1174 for (int i = 0; i < joinedTextLength; i++)
1175 characterIndices[i] = i; }
1176 int[] interCharacterIndices = new int[joinedTextLength - 1]; {
1177 for (int i = 0; i < joinedTextLength - 1; i++)
1178 interCharacterIndices[i] = i + 1; }
1179
1180
1181 Typeform[] _typeform = null;
1182 for (Typeform t : typeform)
1183 if (t != Typeform.PLAIN_TEXT) {
1184 _typeform = new Typeform[joinedTextLength];
1185 for (int i = 0; i < _typeform.length; i++)
1186 _typeform[i] = typeform[textWithWsMapping[joinedTextMapping[i]]];
1187 break; }
1188 try {
1189 TranslationResult r = translator.translate(joinedText, _typeform, characterIndices, interCharacterIndices, displayTable);
1190 joinedBraille = r.getBraille();
1191 if (lengthByCodePoints(joinedBraille) != joinedBraille.length())
1192 throw new RuntimeException();
1193 characterIndicesInBraille = r.getCharacterAttributes();
1194 interCharacterIndicesInBraille = r.getInterCharacterAttributes(); }
1195 catch (TranslationException e) {
1196 throw new RuntimeException(e); }
1197 catch (DisplayException e) {
1198 throw new RuntimeException(e); }
1199 if (partBeforeCurPos != null)
1200 if (!joinedBraille.substring(0, curPosInBraille).equals(partBeforeCurPos))
1201 throw new IllegalStateException();
1202 int newCurPosInBraille = positionInBraille(curPos);
1203 if (curPosInBraille >= 0) {
1204 if (curPosInBraille != newCurPosInBraille)
1205 throw new IllegalStateException();
1206 } else
1207 curPosInBraille = newCurPosInBraille;
1208 endPosInBraille = positionInBraille(endPos);
1209 }
1210 }
1211 }
1212
1213 private List<CSSStyledText> transform(java.lang.Iterable<CSSStyledText> styledText,
1214 boolean forceBraille,
1215 boolean failWhenNonStandardHyphenation) throws NonStandardHyphenationException {
1216 try {
1217 if (fullHyphenator == compoundWordHyphenator)
1218 if (any(styledText, t -> {
1219 SimpleInlineStyle style = t.getStyle();
1220 return style != null && style.getProperty("hyphens") == Hyphens.AUTO; }))
1221 logger.warn("hyphens: auto not supported");
1222 styledText = fullHyphenator.transform(styledText); }
1223 catch (NonStandardHyphenationException e) {
1224 if (failWhenNonStandardHyphenation)
1225 throw e;
1226 else
1227 switch (handleNonStandardHyphenation) {
1228 case NON_STANDARD_HYPH_IGNORE:
1229 logger.warn("hyphens: auto can not be applied due to non-standard hyphenation points.");
1230 break;
1231 case NON_STANDARD_HYPH_FAIL:
1232 logger.error("hyphens: auto can not be applied due to non-standard hyphenation points.");
1233 throw e;
1234 case NON_STANDARD_HYPH_DEFER:
1235 if (forceBraille) {
1236 logger.error("hyphens: auto can not be applied due to non-standard hyphenation points.");
1237 throw e; }
1238 logger.debug("Deferring hyphenation to formatting phase due to non-standard hyphenation points.");
1239
1240
1241 List<CSSStyledText> result = new ArrayList<>();
1242 for (CSSStyledText t : styledText) result.add(t);
1243 return result; }}
1244 int size = size(styledText);
1245 String[] text = new String[size];
1246 SimpleInlineStyle[] style = new SimpleInlineStyle[size];
1247 int i = 0;
1248 for (CSSStyledText t : styledText) {
1249 text[i] = t.getText();
1250 style[i] = t.getStyle();
1251 if (style[i] != null) {
1252 style[i] = (SimpleInlineStyle)style[i].clone();
1253 style[i].removeProperty("hyphens"); }
1254 i++; }
1255 String[] braille = transform(text, style);
1256 List<CSSStyledText> result = new ArrayList<>();
1257 i = 0;
1258 for (CSSStyledText t : styledText) {
1259
1260 List<PropertyValue> s = null; {
1261 if (style[i] == null) {
1262 if (s == null) s = new ArrayList<>();
1263 s.add(TEXT_TRANSFORM_NONE);
1264 if (table.usesCustomDisplayTable())
1265 s.add(BRAILLE_CHARSET_CUSTOM);
1266 } else {
1267 if (style[i].getProperty("text-transform") != TextTransform.NONE) {
1268 style[i].removeProperty("text-transform");
1269 if (s == null) s = new ArrayList<>();
1270 s.add(TEXT_TRANSFORM_NONE);
1271 }
1272 BrailleCharset bc = style[i].getProperty("braille-charset");
1273 if (table.usesCustomDisplayTable()) {
1274 if (bc != BrailleCharset.CUSTOM) {
1275 if (bc != null)
1276 style[i].removeProperty("braille-charset");
1277 if (s == null) s = new ArrayList<>();
1278 s.add(BRAILLE_CHARSET_CUSTOM);
1279 }
1280 } else
1281 if (bc != null && bc != BrailleCharset.UNICODE)
1282 style[i].removeProperty("braille-charset");
1283 }
1284 }
1285 if (s != null) {
1286 if (style[i] != null)
1287 style[i].forEach(s::add);
1288 style[i] = new SimpleInlineStyle(s);
1289 }
1290 Map<String,String> a = t.getTextAttributes();
1291 if (a != null)
1292 a = new HashMap<>(a);
1293 Locale lang = t.getLanguage();
1294 if (lang != null)
1295 if (includeBrailleCodeInLanguage)
1296 lang = getBrailleCode().toLanguageTag(lang);
1297 else
1298 lang = new Locale.Builder().setLocale(lang)
1299 .setScript("Brai")
1300 .build();
1301 result.add(new CSSStyledText(braille[i], style[i], lang, a));
1302 i++;
1303 }
1304 return result;
1305 }
1306
1307
1308
1309
1310
1311 private String[] transform(String[] text, SimpleInlineStyle[] styles) {
1312 int size = text.length;
1313 Typeform[] typeform = new Typeform[size];
1314 boolean[] preserveLines = new boolean[size];
1315 boolean[] preserveSpace = new boolean[size];
1316 int[] letterSpacing = new int[size];
1317 for (int i = 0; i < size; i++) {
1318 typeform[i] = Typeform.PLAIN_TEXT;
1319 preserveLines[i] = preserveSpace[i] = false;
1320 letterSpacing[i] = 0;
1321 SimpleInlineStyle style = styles[i];
1322 if (style != null) {
1323 CSSProperty val = style.getProperty("white-space");
1324 if (val != null) {
1325 if (val == WhiteSpace.PRE_WRAP)
1326 preserveLines[i] = preserveSpace[i] = true;
1327 else if (val == WhiteSpace.PRE_LINE)
1328 preserveLines[i] = true;
1329
1330 }
1331 val = style.getProperty("text-transform");
1332 if (val != null) {
1333 if (val == TextTransform.NONE) {
1334
1335
1336
1337
1338
1339 val = style.getProperty("braille-charset");
1340 if (val != null) {
1341 if (val == BrailleCharset.CUSTOM)
1342
1343 text[i] = displayTable.decode(text[i]); }
1344 continue; }
1345 else if (val == TextTransform.AUTO) {}
1346 else if (val == TextTransform.list_values) {
1347 TermList values = style.getValue(TermList.class, "text-transform");
1348 text[i] = textFromTextTransform(text[i], values);
1349 typeform[i] = typeform[i].add(typeformFromTextTransform(values, translator, supportedTypeforms)); }}
1350 val = style.getProperty("letter-spacing");
1351 if (val != null) {
1352 if (val == LetterSpacing.length) {
1353 letterSpacing[i] = style.getValue(TermInteger.class, "letter-spacing").getIntValue();
1354 if (letterSpacing[i] < 0) {
1355 logger.warn("{} not supported, must be non-negative", val);
1356 letterSpacing[i] = 0; }}
1357 style.removeProperty("letter-spacing"); }
1358 typeform[i] = typeform[i].add(typeformFromInlineCSS(style, translator, supportedTypeforms));}}
1359
1360 return transform(text, typeform, preserveLines, preserveSpace, letterSpacing);
1361 }
1362
1363 private String[] transform(String[] text, Typeform[] typeform) {
1364 int size = text.length;
1365 boolean[] preserveLines = new boolean[size];
1366 boolean[] preserveSpace = new boolean[size];
1367 int[] letterSpacing = new int[size];
1368 for (int i = 0; i < text.length; i++) {
1369 preserveLines[i] = preserveSpace[i] = false;
1370 letterSpacing[i] = 0; }
1371 return transform(text, typeform, preserveLines, preserveSpace, letterSpacing);
1372 }
1373
1374
1375 private String applyLetterSpacing(String text, int letterSpacing) {
1376 String space = "";
1377 for (int i = 0; i < letterSpacing; i++)
1378 space += NBSP;
1379 return text.replaceAll("\u001F", space);
1380 }
1381
1382 private String[] transform(String[] text,
1383 Typeform[] typeform,
1384 boolean[] preserveLines,
1385 boolean[] preserveSpace,
1386 int[] letterSpacing) {
1387
1388
1389 if (unicodeNormalization != null)
1390 for (int k = 0; k < text.length; k++)
1391 text[k] = Normalizer.normalize(text[k], unicodeNormalization);
1392
1393
1394
1395 String[] textWithWs;
1396
1397
1398 boolean[] pre;
1399
1400 int[] textWithWsMapping; {
1401 List<String> l1 = new ArrayList<String>();
1402 List<Boolean> l2 = new ArrayList<Boolean>();
1403 List<Integer> l3 = new ArrayList<Integer>();
1404 for (int i = 0; i < text.length; i++) {
1405 String t = text[i];
1406 if (t.isEmpty()) {
1407 l1.add(t);
1408 l2.add(false);
1409 l3.add(i); }
1410 else {
1411 Pattern ws;
1412 if (preserveSpace[i])
1413 ws = ON_SPACE_SPLITTER;
1414 else if (preserveLines[i])
1415 ws = LINE_SPLITTER;
1416 else
1417 ws = ON_NBSP_SPLITTER;
1418 boolean p = false;
1419 for (String s : splitInclDelimiter(t, ws)) {
1420 if (!s.isEmpty()) {
1421 l1.add(s);
1422 l2.add(p);
1423 l3.add(i); }
1424 p = !p; }}}
1425 int len = l1.size();
1426 textWithWs = new String[len];
1427 pre = new boolean[len];
1428 textWithWsMapping = new int[len];
1429 for (int i = 0; i < len; i++) {
1430 textWithWs[i] = l1.get(i);
1431 pre[i] = l2.get(i);
1432 textWithWsMapping[i] = l3.get(i); }
1433 }
1434
1435
1436
1437 String joinedText;
1438
1439 int[] joinedTextMapping;
1440
1441
1442 byte[] inputAttrs; {
1443 String[] textWithWsReplaced = new String[textWithWs.length];
1444 for (int i = 0; i < textWithWs.length; i++)
1445 textWithWsReplaced[i] = pre[i] ? ""+NBSP : textWithWs[i];
1446 Tuple2<String,byte[]> t = extractHyphens(join(textWithWsReplaced, RS), true, SHY, ZWSP);
1447 joinedText = t._1;
1448 inputAttrs = t._2;
1449 String[] nohyph = toArray(SEGMENT_SPLITTER.split(joinedText), String.class);
1450 joinedTextMapping = new int[lengthByCodePoints(join(nohyph))];
1451 int i = 0;
1452 int j = 0;
1453 for (String s : nohyph) {
1454 int l = lengthByCodePoints(s);
1455 for (int k = 0; k < l; k++)
1456 joinedTextMapping[i++] = j;
1457 j++; }
1458 t = extractHyphens(inputAttrs, joinedText, true, null, null, null, RS);
1459 joinedText = t._1;
1460 inputAttrs = t._2;
1461 if (joinedText.matches("\\xA0*"))
1462 return text;
1463 if (inputAttrs == null)
1464 inputAttrs = new byte[lengthByCodePoints(joinedText) - 1];
1465 }
1466
1467
1468 boolean someLetterSpacing = false; {
1469 for (int i = 0; i < letterSpacing.length; i++)
1470 if (letterSpacing[i] > 0) someLetterSpacing = true; }
1471 if (someLetterSpacing)
1472
1473 inputAttrs = detectLetterBoundaries(inputAttrs, joinedText, (byte)4);
1474
1475
1476 Typeform[] _typeform = null;
1477 for (Typeform t : typeform)
1478 if (t != Typeform.PLAIN_TEXT) {
1479 _typeform = new Typeform[lengthByCodePoints(joinedText)];
1480 for (int i = 0; i < _typeform.length; i++)
1481 _typeform[i] = typeform[textWithWsMapping[joinedTextMapping[i]]];
1482 break; }
1483
1484
1485 String[] brailleWithWs;
1486 try {
1487
1488
1489 String joinedBrailleWithoutHyphens;
1490 String joinedBraille;
1491 byte[] outputAttrs; {
1492 int[] inputAttrsAsInt = new int[inputAttrs.length];
1493 for (int i = 0; i < inputAttrs.length; i++)
1494 inputAttrsAsInt[i] = inputAttrs[i];
1495 TranslationResult r = translator.translate(joinedText, _typeform, null, inputAttrsAsInt, displayTable);
1496 joinedBrailleWithoutHyphens = r.getBraille();
1497 if (lengthByCodePoints(joinedBrailleWithoutHyphens) != joinedBrailleWithoutHyphens.length())
1498 throw new RuntimeException();
1499 int [] outputAttrsAsInt = r.getInterCharacterAttributes();
1500 if (outputAttrsAsInt != null) {
1501 outputAttrs = new byte[outputAttrsAsInt.length];
1502 for (int i = 0; i < outputAttrs.length; i++)
1503 outputAttrs[i] = (byte)outputAttrsAsInt[i];
1504 joinedBraille = insertHyphens(joinedBrailleWithoutHyphens, outputAttrs, false, SHY, ZWSP, US, RS); }
1505 else {
1506 joinedBraille = joinedBrailleWithoutHyphens;
1507 outputAttrs = null; }
1508 }
1509
1510
1511 if (textWithWs.length == 1)
1512 brailleWithWs = new String[]{joinedBraille};
1513 else {
1514
1515
1516 {
1517 brailleWithWs = new String[textWithWs.length];
1518 int i = 0;
1519 int imax = lengthByCodePoints(joinedText);
1520 int kmax = textWithWs.length;
1521 int k = (i < imax) ? joinedTextMapping[i] : kmax;
1522 int l = 0;
1523 while (l < k) brailleWithWs[l++] = "";
1524 for (String s : SEGMENT_SPLITTER.split(joinedBraille)) {
1525 brailleWithWs[l++] = s;
1526 while (k < l)
1527 k = (++i < imax) ? joinedTextMapping[i] : kmax;
1528 while (l < k)
1529 brailleWithWs[l++] = ""; }
1530 if (l == kmax) {
1531 boolean wsLost = false;
1532 for (k = 0; k < kmax; k++)
1533 if (pre[k]) {
1534 Matcher m = Pattern.compile("\\xA0([\\xAD\\u200B]*)").matcher(brailleWithWs[k]);
1535 if (m.matches())
1536 brailleWithWs[k] = textWithWs[k] + m.group(1);
1537 else
1538 wsLost = true; }
1539 if (wsLost) {
1540 logger.warn("White space was not preserved (see detailed log for more info)");
1541 logger.debug("White space was lost in the output.\n"
1542 + "Input: " + Arrays.toString(textWithWs) + "\n"
1543 + "Output: " + Arrays.toString(brailleWithWs)); }}
1544 else {
1545 logger.warn("Text segmentation was lost (see detailed log for more info)");
1546 logger.debug("Text segmentation was lost in the output. Falling back to fuzzy mode.\n"
1547 + "=> input segments: " + Arrays.toString(textWithWs) + "\n"
1548 + "=> output segments: " + Arrays.toString(Arrays.copyOf(brailleWithWs, l)));
1549 brailleWithWs = null; }
1550 }
1551
1552
1553 if (brailleWithWs == null) {
1554
1555
1556
1557 int[] inputSegmentNumbers = joinedTextMapping;
1558
1559
1560 TranslationResult r = translator.translate(joinedText, _typeform, inputSegmentNumbers, null, displayTable);
1561 if (!r.getBraille().equals(joinedBrailleWithoutHyphens))
1562 throw new RuntimeException("Coding error");
1563 int[] outputSegmentNumbers = r.getCharacterAttributes();
1564 brailleWithWs = new String[textWithWs.length];
1565 boolean wsLost = false;
1566 StringBuffer b = new StringBuffer();
1567 int jmax = joinedBrailleWithoutHyphens.length();
1568 int kmax = textWithWs.length;
1569 int k = joinedTextMapping[0];
1570 int l = 0;
1571 while (l < k)
1572 brailleWithWs[l++] = "";
1573 for (int j = 0; j < jmax; j++) {
1574 if (outputSegmentNumbers[j] > l) {
1575 brailleWithWs[l] = b.toString();
1576 b = new StringBuffer();
1577
1578 if (j > 0 && (outputAttrs[j - 1] & 8) == 8) {
1579 if (pre[l]) {
1580 Matcher m = Pattern.compile("\\xA0([\\xAD\\u200B]*)").matcher(brailleWithWs[l]);
1581 if (m.matches())
1582 brailleWithWs[l] = textWithWs[l] + m.group(1);
1583 else
1584 wsLost = true; }}
1585 else {
1586 if (pre[l])
1587 wsLost = true;
1588 if (l <= kmax && pre[l + 1]) {
1589 pre[l + 1] = false;
1590 wsLost = true; }}
1591 l++;
1592 while (outputSegmentNumbers[j] > l) {
1593 brailleWithWs[l] = "";
1594 if (pre[l])
1595 wsLost = true;
1596 l++; }}
1597 b.append(joinedBrailleWithoutHyphens.charAt(j));
1598 if (j < jmax - 1) {
1599
1600 if ((outputAttrs[j] & 1) == 1)
1601 b.append(SHY);
1602 if ((outputAttrs[j] & 2) == 2)
1603 b.append(ZWSP);
1604 if ((outputAttrs[j] & 4) == 4)
1605 b.append(US); }}
1606 brailleWithWs[l] = b.toString();
1607 if (pre[l])
1608 if (brailleWithWs[l].equals(""+NBSP))
1609 brailleWithWs[l] = textWithWs[l];
1610 else
1611 wsLost = true;
1612 l++;
1613 while (l < kmax) {
1614 if (pre[l])
1615 wsLost = true;
1616 brailleWithWs[l++] = ""; }
1617 if (wsLost) {
1618 logger.warn("White space was not preserved: " + joinedText.replaceAll("\\s+"," "));
1619 logger.debug("White space was lost in the output.\n"
1620 + "Input: " + Arrays.toString(textWithWs) + "\n"
1621 + "Output: " + Arrays.toString(brailleWithWs)); }
1622 }
1623 }
1624 } catch (TranslationException e) {
1625 throw new RuntimeException(e);
1626 } catch (DisplayException e) {
1627 throw new RuntimeException(e); }
1628
1629
1630 String braille[] = new String[text.length];
1631 for (int i = 0; i < braille.length; i++)
1632 braille[i] = "";
1633 for (int j = 0; j < brailleWithWs.length; j++)
1634 braille[textWithWsMapping[j]] += brailleWithWs[j];
1635
1636
1637 if (someLetterSpacing)
1638 for (int i = 0; i < braille.length; i++)
1639 braille[i] = applyLetterSpacing(braille[i], letterSpacing[i]);
1640
1641 return braille;
1642 }
1643
1644
1645
1646
1647 private byte[] detectLetterBoundaries(byte[] addTo, String text, byte val) {
1648 if (addTo == null)
1649 addTo = new byte[lengthByCodePoints(text) - 1];
1650 int i = 0;
1651 int prev = -1;
1652 for (int c : text.codePoints().toArray()) {
1653 if (i > 0 && ((Character.isLetter(c) && Character.isLetter(prev)) ||
1654 c == '-' ||
1655 prev == '-'))
1656 addTo[i - 1] |= val;
1657 if (i < addTo.length && c == '\u00ad')
1658 addTo[i] |= val;
1659 prev = c;
1660 i++;
1661 }
1662 return addTo;
1663 }
1664
1665 @Override
1666 public ToStringHelper toStringHelper() {
1667 return MoreObjects.toStringHelper("LiblouisTranslatorJnaImplProvider$LiblouisTranslatorImpl")
1668 .add("translator", translator)
1669 .add("displayTable", displayTable)
1670 .add("hyphenator", hyphenator);
1671 }
1672
1673 @Override
1674 public int hashCode() {
1675 final int prime = 31;
1676 int hash = 1;
1677 hash = prime * hash + translator.hashCode();
1678 hash = prime * hash + ((hyphenator == null) ? 0 : hyphenator.hashCode());
1679 hash = prime * hash + handleNonStandardHyphenation;
1680 hash = prime * hash + (includeBrailleCodeInLanguage ? 1 : 0);
1681 return hash;
1682 }
1683
1684 @Override
1685 public boolean equals(Object object) {
1686 if (this == object)
1687 return true;
1688 if (object == null)
1689 return false;
1690 if (object.getClass() != LiblouisTranslatorImpl.class)
1691 return false;
1692 LiblouisTranslatorImpl that = (LiblouisTranslatorImpl)object;
1693 if (!this.translator.equals(that.translator))
1694 return false;
1695 if (!this.displayTable.equals(that.displayTable))
1696 return false;
1697 if (this.hyphenator == null && that.hyphenator != null)
1698 return false;
1699 if (this.hyphenator != null && that.hyphenator == null)
1700 return false;
1701 if (!this.hyphenator.equals(that.hyphenator))
1702 return false;
1703 return true;
1704 }
1705 }
1706
1707 private class HandleTextTransformNone extends CompoundBrailleTranslator implements LiblouisTranslator {
1708
1709 final LiblouisTranslator translator;
1710
1711 HandleTextTransformNone(LiblouisTranslator translator, BrailleTranslator unityTranslator) {
1712 super(translator, ImmutableMap.of("none", () -> unityTranslator));
1713 this.translator = translator;
1714 }
1715
1716 private HandleTextTransformNone(CompoundBrailleTranslator from, LiblouisTranslator translator) {
1717 super(from);
1718 this.translator = translator;
1719 }
1720
1721 @Override
1722 public HandleTextTransformNone _withHyphenator(Hyphenator hyphenator) {
1723 HandleTextTransformNone t = new HandleTextTransformNone(
1724 (CompoundBrailleTranslator)super._withHyphenator(hyphenator),
1725 translator);
1726 LiblouisTranslatorJnaImplProvider.this.rememberId(t);
1727 return t;
1728 }
1729
1730 @Override
1731 public LiblouisTable asLiblouisTable() {
1732 return translator.asLiblouisTable();
1733 }
1734
1735 @Override
1736 public FromTypeformedTextToBraille fromTypeformedTextToBraille() {
1737 return translator.fromTypeformedTextToBraille();
1738 }
1739 }
1740
1741 private interface FullHyphenator extends Hyphenator.FullHyphenator {
1742 public byte[] hyphenate(String text, Locale language);
1743 }
1744
1745
1746
1747
1748
1749 private final static FullHyphenator compoundWordHyphenator = new CompoundWordHyphenator();
1750
1751 private static class CompoundWordHyphenator extends NoHyphenator implements FullHyphenator {
1752
1753 public byte[] hyphenate(String text, Locale language) {
1754 if (text.isEmpty())
1755 return null;
1756 Tuple2<String,byte[]> t = extractHyphens(text, true, SHY, ZWSP);
1757 if (t._1.isEmpty())
1758 return null;
1759 return transform(t._2, t._1, language);
1760 }
1761 }
1762
1763 private static class HyphenatorAsFullHyphenator implements FullHyphenator {
1764
1765 private final Hyphenator.FullHyphenator hyphenator;
1766
1767 private HyphenatorAsFullHyphenator(Hyphenator hyphenator) {
1768 this.hyphenator = hyphenator.asFullHyphenator();
1769 }
1770
1771 public java.lang.Iterable<CSSStyledText> transform(java.lang.Iterable<CSSStyledText> text) {
1772 return hyphenator.transform(text);
1773 }
1774
1775 private final static SimpleInlineStyle HYPHENS_AUTO = cssParser.parse("hyphens: auto");
1776
1777 public byte[] hyphenate(String text, Locale language) {
1778 return extractHyphens(
1779 hyphenator.transform(singleton(new CSSStyledText(text, HYPHENS_AUTO, language))).iterator().next().getText(),
1780 true, SHY, ZWSP)._2;
1781 }
1782 }
1783
1784
1785
1786
1787
1788
1789 protected static Typeform typeformFromInlineCSS(SimpleInlineStyle style, Translator table, Map<String,Typeform> supportedTypeforms) {
1790 Typeform typeform = Typeform.PLAIN_TEXT;
1791 for (String prop : style.getPropertyNames()) {
1792 if (prop.equals("font-style")) {
1793 CSSProperty value = style.getProperty(prop);
1794 if (value == FontStyle.ITALIC || value == FontStyle.OBLIQUE) {
1795 Typeform t = supportedTypeforms.get("italic");
1796 if (t != null)
1797 typeform = typeform.add(t);
1798 else
1799 logger.warn("{} not supported: emphclass 'italic' not defined in table {}",
1800 style.get(prop),
1801 table.getTable());
1802 style.removeProperty(prop);
1803 continue; }}
1804 else if (prop.equals("font-weight")) {
1805 CSSProperty value = style.getProperty(prop);
1806 if (value == FontWeight.BOLD) {
1807 Typeform t = supportedTypeforms.get("bold");
1808 if (t != null)
1809 typeform = typeform.add(t);
1810 else
1811 logger.warn("{} not supported: emphclass 'bold' not defined in table {}",
1812 style.get(prop),
1813 table.getTable());
1814 style.removeProperty(prop);
1815 continue; }}
1816 else if (prop.equals("text-decoration")) {
1817 CSSProperty value = style.getProperty(prop);
1818 if (value == TextDecoration.UNDERLINE) {
1819 Typeform t = supportedTypeforms.get("underline");
1820 if (t != null)
1821 typeform = typeform.add(t);
1822 else
1823 logger.warn("{} not supported: emphclass 'underline' not defined in table {}",
1824 style.get(prop),
1825 table.getTable());
1826 style.removeProperty(prop);
1827 continue; }}}
1828 return typeform;
1829 }
1830
1831
1832
1833
1834
1835
1836 protected static String textFromTextTransform(String text, TermList textTransform) {
1837 for (Term<?> t : textTransform) {
1838 String tt = ((TermIdent)t).getValue();
1839 if (tt.equals("uppercase"))
1840 text = text.toUpperCase();
1841 else if (tt.equals("lowercase"))
1842 text = text.toLowerCase();
1843 else if (!LOUIS_TEXT_TRANSFORM.matcher(tt).matches())
1844 logger.warn("text-transform: {} not supported", tt);
1845 }
1846 return text;
1847 }
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860 protected static Typeform typeformFromTextTransform(TermList textTransform, Translator table, Map<String,Typeform> supportedTypeforms) {
1861 Typeform typeform = Typeform.PLAIN_TEXT;
1862 for (Term<?> t : textTransform) {
1863 String tt = ((TermIdent)t).getValue();
1864 Matcher m = LOUIS_TEXT_TRANSFORM.matcher(tt);
1865 if (m.matches()) {
1866 String emphClass = m.group("class");
1867 if (emphClass.equals("computer") || emphClass.equals("comp")) {
1868 typeform = typeform.add(Typeform.COMPUTER);
1869 } else {
1870 if (emphClass.equals("ital"))
1871 emphClass= "italic";
1872 else if (emphClass.equals("under"))
1873 emphClass = "underline";
1874 Typeform tf = supportedTypeforms.get(emphClass);
1875 if (tf != null)
1876 typeform = typeform.add(tf);
1877 else
1878 logger.warn("text-transform: {} not supported: emphclass '{}' not defined in table {}",
1879 tt,
1880 emphClass,
1881 table.getTable());
1882 }
1883 continue;
1884 } else if (tt.equals("uppercase") || tt.equals("lowercase")) {
1885
1886 continue;
1887 }
1888 logger.warn("text-transform: {} not supported", tt);
1889 }
1890 return typeform;
1891 }
1892
1893 private final static Pattern LOUIS_TEXT_TRANSFORM = Pattern.compile("^-?(lib)?louis-(?<class>.+)$");
1894
1895 @SuppressWarnings("unused")
1896 private static int mod(int a, int n) {
1897 int result = a % n;
1898 if (result < 0)
1899 result += n;
1900 return result;
1901 }
1902
1903 private static int lengthByCodePoints(String s) {
1904 return s.codePointCount(0, s.length());
1905 }
1906
1907 private static String substringByCodePoints(String s, int beginIndex) {
1908 return s.substring(s.offsetByCodePoints(0, beginIndex));
1909 }
1910
1911 private static String substringByCodePoints(String s, int beginIndex, int endIndex) {
1912 return s.substring(s.offsetByCodePoints(0, beginIndex), s.offsetByCodePoints(0, endIndex));
1913 }
1914
1915 private static class LineBreakSolution {
1916 String line;
1917 String replacementWord;
1918 String lineInBraille;
1919 int lineInBrailleLength;
1920 }
1921
1922 private static final Logger logger = LoggerFactory.getLogger(LiblouisTranslatorJnaImplProvider.class);
1923
1924 }