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