1 package org.daisy.pipeline.braille.dotify.impl;
2
3 import java.util.ArrayList;
4 import java.util.Arrays;
5 import java.util.Collections;
6 import java.util.HashMap;
7 import java.util.List;
8 import java.util.Locale;
9 import java.util.Map;
10 import java.util.NoSuchElementException;
11
12 import com.google.common.base.MoreObjects;
13 import com.google.common.base.MoreObjects.ToStringHelper;
14 import com.google.common.collect.ImmutableList;
15 import static com.google.common.collect.Iterables.size;
16
17 import cz.vutbr.web.css.CSSProperty;
18
19 import org.daisy.braille.css.BrailleCSSProperty.Hyphens;
20 import org.daisy.braille.css.SimpleInlineStyle;
21
22 import org.daisy.dotify.api.translator.BrailleFilter;
23 import org.daisy.dotify.api.translator.BrailleFilterFactoryService;
24 import org.daisy.dotify.api.translator.Translatable;
25 import org.daisy.dotify.api.translator.TranslationException;
26 import org.daisy.dotify.api.translator.TranslatorConfigurationException;
27 import org.daisy.dotify.api.translator.TranslatorMode;
28 import org.daisy.dotify.api.translator.TranslatorType;
29
30 import org.daisy.pipeline.braille.common.AbstractBrailleTranslator;
31 import org.daisy.pipeline.braille.common.AbstractBrailleTranslator.util.DefaultLineBreaker;
32 import org.daisy.pipeline.braille.common.AbstractTransformProvider;
33 import org.daisy.pipeline.braille.common.AbstractTransformProvider.util.Function;
34 import org.daisy.pipeline.braille.common.AbstractTransformProvider.util.Iterables;
35 import static org.daisy.pipeline.braille.common.AbstractTransformProvider.util.Iterables.concat;
36 import static org.daisy.pipeline.braille.common.AbstractTransformProvider.util.logCreate;
37 import static org.daisy.pipeline.braille.common.AbstractTransformProvider.util.logSelect;
38 import org.daisy.pipeline.braille.common.BrailleTranslatorProvider;
39 import org.daisy.pipeline.braille.common.Hyphenator;
40 import org.daisy.pipeline.braille.common.Query;
41 import org.daisy.pipeline.braille.common.Query.Feature;
42 import org.daisy.pipeline.braille.common.Query.MutableQuery;
43 import static org.daisy.pipeline.braille.common.Query.util.mutableQuery;
44 import org.daisy.pipeline.braille.common.TransformProvider;
45 import static org.daisy.pipeline.braille.common.TransformProvider.util.varyLocale;
46 import static org.daisy.pipeline.braille.common.util.Locales.parseLocale;
47 import static org.daisy.pipeline.braille.common.util.Strings.extractHyphens;
48 import static org.daisy.pipeline.braille.common.util.Strings.join;
49 import org.daisy.pipeline.braille.css.CSSStyledText;
50 import org.daisy.pipeline.braille.css.TextStyleParser;
51 import org.daisy.pipeline.braille.dotify.DotifyTranslator;
52
53 import org.osgi.framework.FrameworkUtil;
54
55 import org.osgi.service.component.annotations.Component;
56 import org.osgi.service.component.annotations.Reference;
57 import org.osgi.service.component.annotations.ReferenceCardinality;
58 import org.osgi.service.component.annotations.ReferencePolicy;
59
60 import org.slf4j.Logger;
61 import org.slf4j.LoggerFactory;
62
63
64
65
66 public class DotifyTranslatorImpl extends AbstractBrailleTranslator implements DotifyTranslator {
67
68 private final static char SHY = '\u00AD';
69 private final static char ZWSP = '\u200B';
70
71 private final BrailleFilter filter;
72 private final boolean hyphenating;
73 private final Hyphenator externalHyphenator;
74 private final Provider provider;
75
76 private DotifyTranslatorImpl(BrailleFilter filter, boolean hyphenating, Provider provider) {
77 this.filter = filter;
78 this.hyphenating = hyphenating;
79 this.externalHyphenator = null;
80 this.provider = provider;
81 }
82
83 private DotifyTranslatorImpl(BrailleFilter filter, Hyphenator externalHyphenator, Provider provider) {
84 super(externalHyphenator, null);
85 this.filter = filter;
86 this.hyphenating = true;
87 this.externalHyphenator = externalHyphenator;
88 this.provider = provider;
89 }
90
91 private DotifyTranslatorImpl(DotifyTranslatorImpl from, Hyphenator hyphenator) {
92 super(from);
93 this.filter = from.filter;
94 this.hyphenating = hyphenator != null;
95 this.externalHyphenator = hyphenator;
96 this.provider = from.provider;
97 }
98
99 public BrailleFilter asBrailleFilter() {
100 return filter;
101 }
102
103 @Override
104 public DotifyTranslatorImpl _withHyphenator(Hyphenator hyphenator) {
105 DotifyTranslatorImpl t = new DotifyTranslatorImpl(this, hyphenator);
106 provider.rememberId(t);
107 return t;
108 }
109
110 @Override
111 public FromStyledTextToBraille fromStyledTextToBraille() {
112 return fromStyledTextToBraille;
113 }
114
115 private final FromStyledTextToBraille fromStyledTextToBraille = new FromStyledTextToBraille() {
116 public java.lang.Iterable<CSSStyledText> transform(java.lang.Iterable<CSSStyledText> styledText, int from, int to) {
117 int size = size(styledText);
118 if (to < 0) to = size;
119 CSSStyledText[] braille = new CSSStyledText[to - from];
120 int i = 0;
121 for (CSSStyledText t : styledText) {
122 if (i >= from && i < to) {
123 SimpleInlineStyle style = t.getStyle();
124 if (style != null)
125 style = (SimpleInlineStyle)style.clone();
126 String b = DotifyTranslatorImpl.this.transform(t.getText(), style);
127 Map<String,String> a = t.getTextAttributes();
128 if (a != null)
129 a = new HashMap<>(a);
130 braille[i - from] = new CSSStyledText(b, style, a);
131 }
132 i++; }
133 return Arrays.asList(braille);
134 }
135 };
136
137 @Override
138 public LineBreakingFromStyledText lineBreakingFromStyledText() {
139 return lineBreakingFromStyledText;
140 }
141
142 private final LineBreakingFromStyledText lineBreakingFromStyledText
143 = new DefaultLineBreaker() {
144 protected BrailleStream translateAndHyphenate(final java.lang.Iterable<CSSStyledText> styledText, int from, int to) {
145 List<String> braille = new ArrayList<>();
146 for (CSSStyledText t : fromStyledTextToBraille.transform(styledText, from, to)) {
147 braille.add(t.getText());
148 SimpleInlineStyle s = t.getStyle();
149 if (s != null)
150 for (String prop : s.getPropertyNames())
151 logger.warn("{}: {} not supported", prop, s.get(prop));
152 }
153 return new FullyHyphenatedAndTranslatedString(join(braille));
154 }
155 };
156
157 private final static SimpleInlineStyle HYPHENS_AUTO = TextStyleParser.getInstance().parse("hyphens: auto");
158
159 private String transform(String text, boolean hyphenate) {
160 if (hyphenate && !hyphenating)
161 throw new RuntimeException("'hyphens: auto' is not supported");
162 try {
163 if (hyphenate && externalHyphenator != null) {
164 String hyphenatedText = externalHyphenator.asFullHyphenator().transform(
165 Collections.singleton(new CSSStyledText(text, HYPHENS_AUTO))).iterator().next().getText();
166 return filter.filter(Translatable.text(hyphenatedText).hyphenate(false).build());
167 } else
168 return filter.filter(Translatable.text(text).hyphenate(hyphenate).build()); }
169 catch (TranslationException e) {
170 throw new RuntimeException(e); }
171 }
172
173 private String transform(String text, SimpleInlineStyle style) {
174 boolean hyphenate = false;
175 if (style != null) {
176 CSSProperty val = style.getProperty("hyphens");
177 if (val != null) {
178 if (val == Hyphens.AUTO)
179 hyphenate = true;
180 else if (val == Hyphens.NONE)
181 text = extractHyphens(text, false, SHY, ZWSP)._1;
182 style.removeProperty("hyphens"); }}
183 return transform(text, hyphenate);
184 }
185
186 @Component(
187 name = "org.daisy.pipeline.braille.dotify.DotifyTranslatorImpl.Provider",
188 service = {
189 DotifyTranslator.Provider.class,
190 BrailleTranslatorProvider.class,
191 TransformProvider.class
192 }
193 )
194 public static class Provider extends AbstractTransformProvider<DotifyTranslator>
195 implements DotifyTranslator.Provider {
196
197 public Iterable<DotifyTranslator> _get(Query query) {
198 MutableQuery q = mutableQuery(query);
199 for (Feature f : q.removeAll("input"))
200 if (!supportedInput.contains(f.getValue().get()))
201 return empty;
202 for (Feature f : q.removeAll("output"))
203 if (!supportedOutput.contains(f.getValue().get()))
204 return empty;
205 if (q.containsKey("translator"))
206 if (!"dotify".equals(q.removeOnly("translator").getValue().get()))
207 return empty;
208 return logSelect(q, _provider);
209 }
210
211 protected DotifyTranslator rememberId(DotifyTranslator t) {
212 return super.rememberId(t);
213 }
214
215 private final static Iterable<DotifyTranslator> empty = Iterables.<DotifyTranslator>empty();
216
217
218
219 private final static List<String> supportedInput = Collections.emptyList();
220 private final static List<String> supportedOutput = ImmutableList.of("braille");
221
222 private TransformProvider<DotifyTranslator> _provider
223 = varyLocale(
224 new AbstractTransformProvider<DotifyTranslator>() {
225 public Iterable<DotifyTranslator> _get(Query query) {
226 MutableQuery q = mutableQuery(query);
227 if (q.containsKey("language")
228 || q.containsKey("region")
229 || q.containsKey("locale")
230 || q.containsKey("document-locale")) {
231 final Locale documentLocale;
232 final Locale locale; {
233 try {
234 documentLocale = q.containsKey("document-locale")
235 ? parseLocale(q.removeOnly("document-locale").getValue().get())
236 : null;
237 if (q.containsKey("locale"))
238 locale = parseLocale(q.removeOnly("locale").getValue().get());
239 else {
240 Locale language = q.containsKey("language")
241 ? parseLocale(q.removeOnly("language").getValue().get())
242 : documentLocale;
243 Locale region = q.containsKey("region")
244 ? parseLocale(q.removeOnly("region").getValue().get())
245 : null;
246 if (region != null) {
247
248
249
250 if (language != null)
251 if (!language.equals(new Locale(language.getLanguage()))
252 || !region.getLanguage().equals(language.getLanguage()))
253 return empty;
254 locale = region;
255 } else if (language != null)
256 locale = language;
257 else
258 return empty;
259 }
260 } catch (IllegalArgumentException e) {
261 logger.error("Invalid locale", e);
262 return empty; }
263 }
264 final String mode = TranslatorMode.Builder.withType(TranslatorType.UNCONTRACTED).build().toString();
265 if (!q.isEmpty()) {
266 logger.warn("Unsupported feature '"+ q.iterator().next().getKey() + "'");
267 return empty; }
268 Iterable<BrailleFilter> filters = Iterables.transform(
269 factoryServices,
270 new Function<BrailleFilterFactoryService,BrailleFilter>() {
271 public BrailleFilter _apply(BrailleFilterFactoryService service) {
272 try {
273 if (service.supportsSpecification(locale.toLanguageTag(), mode))
274 return service.newFactory().newFilter(locale.toLanguageTag(), mode); }
275 catch (TranslatorConfigurationException e) {
276 logger.error("Could not create BrailleFilter for locale " + locale + " and mode " + mode, e); }
277 throw new NoSuchElementException(); }});
278 return concat(
279 Iterables.transform(
280 filters,
281 new Function<BrailleFilter,Iterable<DotifyTranslator>>() {
282 public Iterable<DotifyTranslator> _apply(final BrailleFilter filter) {
283 Iterable<DotifyTranslator> translators = empty;
284 if (documentLocale != null && locale.getLanguage().equals(documentLocale.getLanguage()))
285 translators = concat(
286 translators,
287 Iterables.of(
288 logCreate((DotifyTranslator)new DotifyTranslatorImpl(filter, true, Provider.this))));
289 translators = concat(
290 translators,
291 Iterables.of(
292 logCreate((DotifyTranslator)new DotifyTranslatorImpl(filter, false, Provider.this))));
293 return translators;
294 }
295 }
296 )
297 );
298 }
299 return empty;
300 }
301 }
302 );
303
304 private final List<BrailleFilterFactoryService> factoryServices = new ArrayList<BrailleFilterFactoryService>();
305
306 @Reference(
307 name = "BrailleFilterFactoryService",
308 unbind = "-",
309 service = BrailleFilterFactoryService.class,
310 cardinality = ReferenceCardinality.MULTIPLE,
311 policy = ReferencePolicy.STATIC
312 )
313 protected void bindBrailleFilterFactoryService(BrailleFilterFactoryService service) {
314 if (!OSGiHelper.inOSGiContext())
315 service.setCreatedWithSPI();
316 factoryServices.add(service);
317 invalidateCache();
318 }
319
320 protected void unbindBrailleFilterFactoryService(BrailleFilterFactoryService service) {
321 factoryServices.remove(service);
322 invalidateCache();
323 }
324
325 @Override
326 public ToStringHelper toStringHelper() {
327 return MoreObjects.toStringHelper("DotifyTranslatorImpl$Provider");
328 }
329 }
330
331 private static final Logger logger = LoggerFactory.getLogger(DotifyTranslatorImpl.class);
332
333 private static abstract class OSGiHelper {
334 static boolean inOSGiContext() {
335 try {
336 return FrameworkUtil.getBundle(OSGiHelper.class) != null;
337 } catch (NoClassDefFoundError e) {
338 return false;
339 }
340 }
341 }
342 }