1   package org.daisy.pipeline.braille.dotify.calabash.impl;
2   
3   import java.io.ByteArrayInputStream;
4   import java.io.ByteArrayOutputStream;
5   import java.io.InputStream;
6   import java.lang.reflect.InvocationTargetException;
7   import java.util.ArrayList;
8   import java.util.HashMap;
9   import java.util.List;
10  import java.util.Map;
11  import java.util.NoSuchElementException;
12  
13  import javax.xml.transform.stream.StreamSource;
14  
15  import com.google.common.collect.Iterables;
16  
17  import com.xmlcalabash.core.XProcException;
18  import com.xmlcalabash.core.XProcRuntime;
19  import com.xmlcalabash.io.ReadablePipe;
20  import com.xmlcalabash.io.WritablePipe;
21  import com.xmlcalabash.library.DefaultStep;
22  import com.xmlcalabash.model.RuntimeValue;
23  import com.xmlcalabash.runtime.XAtomicStep;
24  
25  import net.sf.saxon.s9api.QName;
26  import net.sf.saxon.s9api.SaxonApiException;
27  import net.sf.saxon.s9api.Serializer;
28  import net.sf.saxon.s9api.XdmNode;
29  
30  import org.daisy.braille.css.InlineStyle;
31  import org.daisy.braille.css.RuleCounterStyle;
32  import org.daisy.common.xproc.calabash.XProcStep;
33  import org.daisy.common.xproc.calabash.XProcStepProvider;
34  import org.daisy.common.xproc.XProcMonitor;
35  import org.daisy.dotify.api.engine.FormatterEngine;
36  import org.daisy.dotify.api.engine.FormatterEngineFactoryService;
37  import org.daisy.dotify.api.formatter.FormatterConfiguration;
38  import org.daisy.dotify.api.formatter.FormatterFactory;
39  import org.daisy.dotify.api.obfl.ObflParserFactoryService;
40  import org.daisy.dotify.api.table.BrailleConverter;
41  import org.daisy.dotify.api.table.Table;
42  import org.daisy.dotify.api.translator.TextBorderConfigurationException;
43  import org.daisy.dotify.api.translator.TextBorderFactory;
44  import org.daisy.dotify.api.translator.TextBorderFactoryMakerService;
45  import org.daisy.dotify.api.translator.TextBorderFactoryService;
46  import org.daisy.dotify.api.translator.TextBorderStyle;
47  import org.daisy.dotify.api.writer.MetaDataItem;
48  import org.daisy.dotify.api.writer.PagedMediaWriter;
49  import org.daisy.dotify.api.writer.PagedMediaWriterConfigurationException;
50  import org.daisy.pipeline.braille.common.BrailleTranslator;
51  import org.daisy.pipeline.braille.common.BrailleTranslatorRegistry;
52  import org.daisy.pipeline.braille.common.Query;
53  import static org.daisy.pipeline.braille.common.Query.util.mutableQuery;
54  import static org.daisy.pipeline.braille.common.Query.util.query;
55  import static org.daisy.pipeline.braille.common.TransformProvider.util.logCreate;
56  import org.daisy.pipeline.braille.common.util.Function0;
57  import org.daisy.pipeline.braille.common.util.Functions;
58  import org.daisy.pipeline.braille.dotify.impl.CounterHandlingBrailleTranslator;
59  import org.daisy.pipeline.braille.pef.TableRegistry;
60  import org.daisy.pipeline.css.CounterStyle;
61  
62  import org.osgi.framework.FrameworkUtil;
63  
64  import org.osgi.service.component.annotations.Activate;
65  import org.osgi.service.component.annotations.Component;
66  import org.osgi.service.component.annotations.Reference;
67  import org.osgi.service.component.annotations.ReferenceCardinality;
68  import org.osgi.service.component.annotations.ReferencePolicy;
69  
70  import org.slf4j.Logger;
71  import org.slf4j.LoggerFactory;
72  
73  /**
74   * Implementation of the <code>{http://code.google.com/p/dotify/}obfl-to-pef</code> step.
75   *
76   * @see <a href="../../../../../../../../resources/xml/library.xpl">The XProc library
77   *      <code>http://www.daisy.org/pipeline/modules/braille/dotify-utils/library.xpl</code></a>.
78   */
79  public class OBFLToPEFStep extends DefaultStep implements XProcStep {
80  	
81  	private static final QName _locale = new QName("locale");
82  	private static final QName _mode = new QName("mode");
83  	private static final QName _braille_charset = new QName("braille-charset");
84  	private static final QName _identifier = new QName("identifier");
85  	private static final QName _style_type = new QName("style-type");
86  	private static final QName _css_text_transform_definitions = new QName("css-text-transform-definitions");
87  	private static final QName _css_hyphenation_resource_definitions = new QName("css-hyphenation-resource-definitions");
88  	private static final QName _css_counter_style_definitions = new QName("css-counter-style-definitions");
89  	private static final QName _has_volume_transition = new QName("has-volume-transition");
90  	
91  	private static final String ERR_NS = "http://www.daisy.org/ns/pipeline/errors";
92  	
93  	/**
94  	 * Code for Dotify errors caused by invalid or unsupported OBFL.
95  	 */
96  	private static final QName ERR_DOTIFY_001 = new QName("pe", ERR_NS, "DOTIFY001");
97  
98  	/**
99  	 * Code for Dotify errors caused by OBFL input that can not be formatted.
100 	 */
101 	private static final QName ERR_DOTIFY_002 = new QName("pe", ERR_NS, "DOTIFY002");
102 
103 	/**
104 	 * Code for unexpected Dotify errors.
105 	 */
106 	private static final QName ERR_DOTIFY_003 = new QName("pe", ERR_NS, "DOTIFY003");
107 
108 	private ReadablePipe source = null;
109 	private WritablePipe result = null;
110 	private final Map<String,String> params = new HashMap<String,String>();
111 	
112 	private final FormatterEngineFactoryService formatterEngineFactoryService;
113 	private final FormatterFactory formatterFactory;
114 	private final ObflParserFactoryService obflParserFactoryService;
115 	private final TextBorderFactoryService textBorderFactoryService;
116 	private final BrailleTranslatorRegistry brailleTranslatorRegistry;
117 	private final TableRegistry tableRegistry;
118 	private final TemporaryBrailleTranslatorProvider temporaryBrailleTranslatorProvider;
119 	
120 	public OBFLToPEFStep(XProcRuntime runtime,
121 	                     XAtomicStep step,
122 	                     FormatterEngineFactoryService formatterEngineFactoryService,
123 	                     FormatterFactory formatterFactory,
124 	                     ObflParserFactoryService obflParserFactoryService,
125 	                     TextBorderFactoryService textBorderFactoryService,
126 	                     BrailleTranslatorRegistry brailleTranslatorRegistry,
127 	                     TableRegistry tableRegistry,
128 	                     TemporaryBrailleTranslatorProvider temporaryBrailleTranslatorProvider) {
129 		super(runtime, step);
130 		this.formatterEngineFactoryService = formatterEngineFactoryService;
131 		this.formatterFactory = formatterFactory;
132 		this.obflParserFactoryService = obflParserFactoryService;
133 		this.textBorderFactoryService = textBorderFactoryService;
134 		this.brailleTranslatorRegistry = brailleTranslatorRegistry;
135 		this.tableRegistry = tableRegistry;
136 		if (temporaryBrailleTranslatorProvider == null) throw new IllegalStateException();
137 		this.temporaryBrailleTranslatorProvider = temporaryBrailleTranslatorProvider;
138 	}
139 	
140 	@Override
141 	public void setInput(String port, ReadablePipe pipe) {
142 		source = pipe;
143 	}
144 	
145 	@Override
146 	public void setOutput(String port, WritablePipe pipe) {
147 		result = pipe;
148 	}
149 	
150 	@Override
151 	public void setParameter(String port, QName name, RuntimeValue value) {
152 		if ("parameters".equals(port))
153 			setParameter(name, value);
154 		else
155 			throw new XProcException("No parameters allowed on port '" + port + "'");
156 	}
157 	
158 	@Override
159 	public void setParameter(QName name, RuntimeValue value) {
160 		if ("".equals(name.getNamespaceURI()))
161 			params.put(name.getLocalName(), value.getString());
162 	}
163 
164 	@Override
165 	public void reset() {
166 		source.resetReader();
167 		result.resetWriter();
168 	}
169 	
170 	@Override
171 	public void run() throws SaxonApiException {
172 		super.run();
173 		Function0<Void> evictTempTranslator = Functions.noOp; // any temporary translators that are created specially
174 		                                                      // for this conversion need to be destroyed afterwards
175 		try {
176 			
177 			XdmNode obflNode = source.read();
178 			String styleType = getOption(_style_type, "");
179 			String mode = getOption(_mode).getString();
180 			String locale = getOption(_locale).getString();
181 			String brailleCharset = getOption(_braille_charset, "");
182 
183 			if (brailleCharset != null && !"".equals(brailleCharset)) {
184 				// if braille-charset is specified we can assume that mode is in query format
185 				Query modeQuery;
186 				try {
187 					modeQuery = query(mode);
188 				} catch (Exception e) {
189 					throw new IllegalArgumentException("Expected mode in query format: " + mode, e);
190 				}
191 				mode = mutableQuery(modeQuery).add("braille-charset", brailleCharset).toString();
192 			}
193 
194 			if ("text/css".equals(styleType)) {
195 				
196 				// We're assuming that no other translators than the DAISY Pipeline implementations
197 				// support text/css.
198 				Query mainQuery;
199 				try {
200 					mainQuery = query(mode);
201 				} catch (Exception e) {
202 					throw new IllegalArgumentException("Expected mode in query format: " + mode, e);
203 				}
204 				mainQuery = mutableQuery(mainQuery).add("input", "text-css").add("output", "braille");
205 				mode = mainQuery.toString();
206 				if (locale != null && !"und".equals(locale))
207 					mainQuery = mutableQuery(mainQuery).add("document-locale", locale);
208 				BrailleTranslator translator; {
209 					String textTransformAndHyphenationResourceDefinitions
210 						= getOption(_css_text_transform_definitions, "")
211 						+ getOption(_css_hyphenation_resource_definitions, "");
212 					try {
213 						translator = (!"".equals(textTransformAndHyphenationResourceDefinitions)
214 							? brailleTranslatorRegistry.getWithHyphenator(mainQuery,
215 							                                              textTransformAndHyphenationResourceDefinitions,
216 							                                              obflNode.getBaseURI(), // note that this is the empty string
217 							                                                                     // if the OBFL came from pxi:css-to-obfl
218 							                                              false)
219 							: brailleTranslatorRegistry.getWithHyphenator(mainQuery)
220 						).iterator().next();
221 					} catch (NoSuchElementException e) {
222 						if (!"".equals(textTransformAndHyphenationResourceDefinitions))
223 							throw new XProcException(
224 								"No translator available for style type '" + styleType + "', mode '" + mode
225 								+ "', locale '" + locale + "' and CSS rules:\n" + textTransformAndHyphenationResourceDefinitions);
226 						else
227 							throw new XProcException(
228 								step.getNode(),
229 								"No translator available for style type '" + styleType + "', mode '" + mode
230 								+ "' and locale '" + locale + "'");
231 					}
232 				}
233 				String counterStyleDefinitions = getOption(_css_counter_style_definitions, "");
234 				Map<String,CounterStyle> counterStyles = "".equals(counterStyleDefinitions)
235 					? null
236 					: CounterStyle.parseCounterStyleRules(
237 						Iterables.filter(new InlineStyle(counterStyleDefinitions), RuleCounterStyle.class));
238 				// handle text-transform: -dotify-counter
239 				translator = new CounterHandlingBrailleTranslator(translator, counterStyles);
240 				translator = logCreate(translator, logger);
241 				evictTempTranslator = temporaryBrailleTranslatorProvider.provideTemporarily(translator);
242 				mode = mutableQuery().add("id", translator.getIdentifier()).toString();
243 				locale = "und";
244 			} else if (!"".equals(styleType)) {
245 				throw new XProcException(step.getNode(), "Value of style-type option not recognized: " + styleType);
246 			}
247 			
248 			// Read OBFL
249 			ByteArrayOutputStream s = new ByteArrayOutputStream();
250 			Serializer serializer = runtime.getProcessor().newSerializer();
251 			serializer.setOutputStream(s);
252 			serializer.setCloseOnCompletion(true);
253 			serializer.serializeNode(obflNode);
254 			serializer.close();
255 			InputStream obflStream = new ByteArrayInputStream(s.toByteArray());
256 			s.close();
257 			
258 			// Convert
259 			String identifier = getOption(_identifier, "");
260 			boolean markCapitalLetters; {
261 				String p = params.get("mark-capital-letters");
262 				markCapitalLetters = (p == null) ? true : !p.equalsIgnoreCase("false"); }
263 			boolean hyphenate; {
264 				String p = params.get("hyphenate");
265 				hyphenate = (p == null) ? true : !p.equalsIgnoreCase("false"); }
266 			boolean allowTextOverflowTrimming; {
267 				String p = params.get("allow-text-overflow-trimming");
268 				allowTextOverflowTrimming = (p == null) ? false : p.equalsIgnoreCase("true"); }
269 			boolean removeStyles; {
270 				String p = params.get("remove-styles");
271 				removeStyles = (p == null) ? false : p.equalsIgnoreCase("true"); }
272 			boolean allowEndingPageOnHyphen;
273 			boolean allowEndingVolumeOnHyphen; {
274 				String p = params.get("hyphenation-at-page-breaks");
275 				if ("false".equalsIgnoreCase(p)) {
276 					allowEndingPageOnHyphen = false;
277 					allowEndingVolumeOnHyphen = false;
278 				} else if ("except-at-volume-breaks".equalsIgnoreCase(p)) {
279 					allowEndingPageOnHyphen = true;
280 					allowEndingVolumeOnHyphen = false;
281 				} else { // true (default)
282 					allowEndingPageOnHyphen = true;
283 					// Follow the OBFL standard which says that "when volume-transition is
284 					// present, the last page or sheet in each volume may be modified so that
285 					// the volume break occurs earlier than usual: preferably between two
286 					// blocks, or if that is not possible, between words"
287 					// (http://braillespecs.github.io/obfl/obfl-specification.html#L8701). In
288 					// other words, volumes should by default not be allowed to end on a hyphen.
289 					allowEndingVolumeOnHyphen = !getOption(_has_volume_transition, false);
290 				}
291 			}
292 			FormatterConfiguration.Builder config = FormatterConfiguration.with(locale, mode)
293 				.markCapitalLetters(markCapitalLetters)
294 				.hyphenate(hyphenate)
295 				.allowsTextOverflowTrimming(allowTextOverflowTrimming)
296 				.allowsEndingPageOnHyphen(allowEndingPageOnHyphen)
297 				.allowsEndingVolumeOnHyphen(allowEndingVolumeOnHyphen);
298 			if (removeStyles)
299 				config.ignoreStyle("em").ignoreStyle("strong");
300 			Table brailleCharsetTable = "".equals(brailleCharset)
301 				? null
302 				: tableRegistry.get(mutableQuery().add("id", brailleCharset)).iterator().next();
303 			FormatterEngine engine = newFormatterEngine(config.build(),
304 			                                            newPEFWriter(identifier, brailleCharsetTable),
305 			                                            brailleCharsetTable == null
306 			                                                ? null
307 			                                                : brailleCharsetTable.newBrailleConverter());
308 			s = new ByteArrayOutputStream();
309 			engine.convert(obflStream, s);
310 			obflStream.close();
311 			InputStream pefStream = new ByteArrayInputStream(s.toByteArray());
312 			s.close();
313 			
314 			// Write PEF
315 			result.write(runtime.getProcessor().newDocumentBuilder().build(new StreamSource(pefStream)));
316 			pefStream.close(); }
317 		
318 		catch (Throwable e) {
319 			String msg = e.getMessage();
320 			if (msg != null)
321 				if ((msg.contains("Cannot fit") && msg.contains("into a margin-region of size"))
322 				    || msg.contains("Failed to solve table"))
323 					throw new XProcException(ERR_DOTIFY_002, step, e);
324 			throw XProcStep.raiseError(e, step); }
325 		finally {
326 			evictTempTranslator.apply(); }
327 	}
328 	
329 	private PagedMediaWriter newPEFWriter(String identifier, Table brailleCharset) throws PagedMediaWriterConfigurationException {
330 		PagedMediaWriter writer = null;
331 		List<MetaDataItem> meta = new ArrayList<MetaDataItem>();
332 		if (brailleCharset != null)
333 			meta.add(new MetaDataItem(new javax.xml.namespace.QName("http://www.daisy.org/ns/pipeline/",
334 			                                                        "ascii-braille-charset",
335 			                                                        "dp2"),
336 			                          brailleCharset.getIdentifier()));
337 		if (!"".equals(identifier))
338 			meta.add(new MetaDataItem(new javax.xml.namespace.QName("http://purl.org/dc/elements/1.1/", "identifier", "dc"),
339 			                          identifier));
340 		writer = new PEFWriter(brailleCharset);
341 		if (!meta.isEmpty())
342 			writer.prepare(meta);
343 		return writer;
344 	}
345 	
346 	private FormatterEngine newFormatterEngine(FormatterConfiguration config,
347 	                                           PagedMediaWriter writer,
348 	                                           BrailleConverter brailleCharset) {
349 		// HACK: If a BrailleConverter is specified, we create a new TextBorderFactoryMakerService
350 		// that uses the default TextBorderFactoryService to create Unicode braille patterns and
351 		// encodes them with the given BrailleConverter. We then make the default
352 		// FormatterEngineFactoryService, FormatterFactory and ObflParserFactoryService use this
353 		// TextBorderFactoryMakerService by using reflection. This assumes that the
354 		// FormatterEngineFactoryService is a
355 		// org.daisy.dotify.formatter.impl.engine.LayoutEngineFactoryImpl, the FormatterFactory is a
356 		// org.daisy.dotify.formatter.impl.FormatterFactoryImpl, and the ObflParserFactoryService is
357 		// a org.daisy.dotify.formatter.impl.obfl.ObflParserFactoryImpl, and that these are the only
358 		// classes that bind a TextBorderFactoryMakerService. If no BrailleConverter is specified,
359 		// we need to reset the TextBorderFactoryMakerService of the default
360 		// FormatterEngineFactoryService, FormatterFactory and ObflParserFactoryService.
361 		TextBorderFactoryMakerService asciiTextBorderFactoryMakerService = new TextBorderFactoryMakerService() {
362 				public TextBorderStyle newTextBorderStyle(Map<String,Object> features)
363 						throws TextBorderConfigurationException {
364 					TextBorderFactory f = textBorderFactoryService.newFactory();
365 					for (String k : features.keySet())
366 						f.setFeature(k, features.get(k));
367 					TextBorderStyle style = f.newTextBorderStyle();
368 					if (brailleCharset == null)
369 						return style;
370 					return new TextBorderStyle.Builder()
371 						.topLeftCorner    (brailleCharset.toText(style.getTopLeftCorner()))
372 						.topBorder        (brailleCharset.toText(style.getTopBorder()))
373 						.topRightCorner   (brailleCharset.toText(style.getTopRightCorner()))
374 						.leftBorder       (brailleCharset.toText(style.getLeftBorder()))
375 						.rightBorder      (brailleCharset.toText(style.getRightBorder()))
376 						.bottomLeftCorner (brailleCharset.toText(style.getBottomLeftCorner()))
377 						.bottomBorder     (brailleCharset.toText(style.getBottomBorder()))
378 						.bottomRightCorner(brailleCharset.toText(style.getBottomRightCorner()))
379 						.build();
380 				}
381 			};
382 		for (Object object : new Object[]{formatterEngineFactoryService,
383 		                                  formatterFactory,
384 		                                  obflParserFactoryService}) {
385 			try {
386 				object.getClass()
387 					.getMethod(object == formatterFactory ? "setTextBorderFactory" : "setTextBorderFactoryMaker",
388 					           TextBorderFactoryMakerService.class)
389 					.invoke(object, asciiTextBorderFactoryMakerService);
390 			} catch (NoSuchMethodException |
391 			         SecurityException |
392 			         IllegalAccessException |
393 			         IllegalArgumentException |
394 			         InvocationTargetException e) {
395 				throw new RuntimeException(e);
396 			}
397 		}
398 		return formatterEngineFactoryService.newFormatterEngine(config, writer);
399 	}
400 	
401 	@Component(
402 		name = "pxi:obfl-to-pef",
403 		service = { XProcStepProvider.class },
404 		property = { "type:String={http://www.daisy.org/ns/pipeline/xproc/internal}obfl-to-pef" }
405 	)
406 	public static class Provider implements XProcStepProvider  {
407 		
408 		@Override
409 		public XProcStep newStep(XProcRuntime runtime, XAtomicStep step, XProcMonitor monitor, Map<String,String> properties) {
410 			return new OBFLToPEFStep(runtime,
411 			                         step,
412 			                         formatterEngineFactoryService,
413 			                         formatterFactory,
414 			                         obflParserFactoryService,
415 			                         textBorderFactoryService,
416 			                         brailleTranslatorRegistry,
417 			                         tableRegistry,
418 			                         temporaryBrailleTranslatorProvider);
419 		}
420 		
421 		@Activate
422 		protected void connectDotifyServices() {
423 			for (Object object : new Object[]{formatterEngineFactoryService,
424 			                                  obflParserFactoryService}) {
425 				try {
426 					object.getClass()
427 						.getMethod("setFormatterFactory", FormatterFactory.class)
428 						.invoke(object, formatterFactory);
429 				} catch (NoSuchMethodException |
430 				         SecurityException |
431 				         IllegalAccessException |
432 				         IllegalArgumentException |
433 				         InvocationTargetException e) {
434 					throw new RuntimeException(e);
435 				}
436 			}
437 			try {
438 				formatterEngineFactoryService.getClass()
439 					.getMethod("setObflParserFactory", ObflParserFactoryService.class)
440 					.invoke(formatterEngineFactoryService, obflParserFactoryService);
441 			} catch (NoSuchMethodException |
442 			         SecurityException |
443 			         IllegalAccessException |
444 			         IllegalArgumentException |
445 			         InvocationTargetException e) {
446 				throw new RuntimeException(e);
447 			}
448 		}
449 		
450 		private FormatterEngineFactoryService formatterEngineFactoryService;
451 		
452 		@Reference(
453 			name = "FormatterEngineFactoryService",
454 			unbind = "-",
455 			service = FormatterEngineFactoryService.class,
456 			cardinality = ReferenceCardinality.MANDATORY,
457 			policy = ReferencePolicy.STATIC
458 		)
459 		protected void bindFormatterEngineFactoryService(FormatterEngineFactoryService service) {
460 			if (!OSGiHelper.inOSGiContext())
461 				service.setCreatedWithSPI();
462 			formatterEngineFactoryService = service;
463 		}
464 		
465 		private FormatterFactory formatterFactory;
466 		
467 		@Reference(
468 			name = "FormatterFactory",
469 			unbind = "-",
470 			service = FormatterFactory.class,
471 			cardinality = ReferenceCardinality.MANDATORY,
472 			policy = ReferencePolicy.STATIC
473 		)
474 		protected void bindFormatterFactory(FormatterFactory factory) {
475 			if (!OSGiHelper.inOSGiContext())
476 				factory.setCreatedWithSPI();
477 			formatterFactory = factory;
478 		}
479 		
480 		private ObflParserFactoryService obflParserFactoryService;
481 		
482 		@Reference(
483 			name = "ObflParserFactoryService",
484 			unbind = "-",
485 			service = ObflParserFactoryService.class,
486 			cardinality = ReferenceCardinality.MANDATORY,
487 			policy = ReferencePolicy.STATIC
488 		)
489 		protected void bindObflParserFactoryService(ObflParserFactoryService service) {
490 			if (!OSGiHelper.inOSGiContext())
491 				service.setCreatedWithSPI();
492 			obflParserFactoryService = service;
493 		}
494 		
495 		private TextBorderFactoryService textBorderFactoryService;
496 		
497 		@Reference(
498 			name = "TextBorderFactoryService",
499 			unbind = "-",
500 			service = TextBorderFactoryService.class,
501 			cardinality = ReferenceCardinality.MANDATORY,
502 			policy = ReferencePolicy.STATIC
503 		)
504 		protected void bindTextBorderFactoryService(TextBorderFactoryService service) {
505 			if (!OSGiHelper.inOSGiContext())
506 				service.setCreatedWithSPI();
507 			textBorderFactoryService = service;
508 		}
509 		
510 		private BrailleTranslatorRegistry brailleTranslatorRegistry;
511 		
512 		@Reference(
513 			name = "BrailleTranslatorRegistry",
514 			unbind = "-",
515 			service = BrailleTranslatorRegistry.class,
516 			cardinality = ReferenceCardinality.MANDATORY,
517 			policy = ReferencePolicy.STATIC
518 		)
519 		protected void bindBrailleTranslatorRegistry(BrailleTranslatorRegistry registry) {
520 			brailleTranslatorRegistry = registry.withContext(logger);
521 			logger.debug("Binding BrailleTranslator registry: {}", registry);
522 		}
523 
524 		private TemporaryBrailleTranslatorProvider temporaryBrailleTranslatorProvider = null;
525 
526 		@Reference(
527 			name = "TemporaryBrailleTranslatorProvider",
528 			unbind = "-",
529 			service = TemporaryBrailleTranslatorProvider.class,
530 			cardinality = ReferenceCardinality.MANDATORY,
531 			policy = ReferencePolicy.STATIC
532 		)
533 		protected void bindTemporaryBrailleTranslatorProvider(TemporaryBrailleTranslatorProvider provider) {
534 			temporaryBrailleTranslatorProvider = provider;
535 		}
536 		
537 		private TableRegistry tableRegistry;
538 		
539 		@Reference(
540 			name = "TableRegistry",
541 			unbind = "-",
542 			service = TableRegistry.class,
543 			cardinality = ReferenceCardinality.MANDATORY,
544 			policy = ReferencePolicy.STATIC
545 		)
546 		protected void bindTableRegistry(TableRegistry registry) {
547 			tableRegistry = registry;
548 		}
549 	}
550 	
551 	private static final Logger logger = LoggerFactory.getLogger(OBFLToPEFStep.class);
552 	
553 	private static abstract class OSGiHelper {
554 		static boolean inOSGiContext() {
555 			try {
556 				return FrameworkUtil.getBundle(OSGiHelper.class) != null;
557 			} catch (NoClassDefFoundError e) {
558 				return false;
559 			}
560 		}
561 	}
562 }