3 namespace Drupal\typogrify\Plugin\Filter;
5 use Drupal\typogrify\SmartyPants;
6 use Drupal\typogrify\Typogrify;
7 use Drupal\typogrify\UnicodeConversion;
8 use Drupal\Core\Form\FormStateInterface;
9 use Drupal\filter\FilterProcessResult;
10 use Drupal\filter\Plugin\FilterBase;
14 * Provides a filter to restrict images to site.
17 * id = "TypogrifyFilter",
18 * title = @Translation("Typogrify"),
19 * description = @Translation("Adds typographic refinements"),
20 * type = Drupal\filter\Plugin\FilterInterface::TYPE_TRANSFORM_IRREVERSIBLE,
22 * "smartypants_enabled" = 1,
23 * "smartypants_hyphens" = 3,
24 * "space_hyphens" = 0,
25 * "wrap_ampersand" = 1,
26 * "widont_enabled"= 1,
27 * "space_to_nbsp" = 1,
28 * "hyphenate_shy" = 0,
31 * "wrap_initial_quotes" = 1,
33 * "ligatures" = "a:0:{}",
34 * "arrows" = "a:0:{}",
35 * "fractions" = "a:0:{}",
36 * "quotes" = "a:0:{}",
41 class TypogrifyFilter extends FilterBase {
44 * The keys in the settings array that are array-valued.
48 protected static $arraySettingsKeys = array(
56 * Serialize array values.
58 * There must be a better way to do this, but it looks as though trying to
59 * save an array-valued plugin setting fails. Our solution is to serialize the
60 * settings before saving and unserialize them before using.
62 * Serialize $settings[$key] for each $key in $arraySettingsKeys.
64 * @param array &$settings
65 * The array of plugin settings.
67 * @see settingsUnserialize()
69 protected static function settingsSerialize(array &$settings) {
70 foreach (static::$arraySettingsKeys as $key) {
71 if (isset($settings[$key]) && is_array($settings[$key])) {
72 $settings[$key] = serialize(array_filter($settings[$key]));
78 * Unserialize array values.
80 * Unserialize $settings[$key] for each $key in $arraySettingsKeys.
82 * @param array &$settings
83 * The array of plugin settings.
85 * @see settingsSerialize()
87 protected static function settingsUnserialize(array &$settings) {
88 foreach (static::$arraySettingsKeys as $key) {
89 if (isset($settings[$key]) && is_string($settings[$key])) {
90 $settings[$key] = unserialize($settings[$key]);
98 public function settingsForm(array $form, FormStateInterface $form_state) {
99 $settings = $this->settings;
100 static::settingsUnserialize($settings);
102 $form['help'] = array(
104 '#value' => '<p>' . t('Enable the following typographic refinements:') . '</p>',
107 // Smartypants settings.
108 $form['smartypants_enabled'] = array(
109 '#type' => 'checkbox',
110 '#title' => t('Use typographers quotation marks and dashes (!smartylink)', array(
111 '!smartylink' => \Drupal::l('SmartyPants', Url::fromUri('http://daringfireball.net/projects/smartypants/')),
113 '#default_value' => $settings['smartypants_enabled'],
116 // Smartypants hyphenation settings.
117 // Uses the same values as the parse attributes in the
118 // SmartyPants::process() function.
119 $form['smartypants_hyphens'] = array(
121 '#title' => t('Hyphenation settings for SmartyPants'),
122 '#default_value' => $settings['smartypants_hyphens'],
124 1 => t('“--” for em-dashes; no en-dash support'),
125 3 => t('“--” for em-dashes; “---” for en-dashes'),
126 2 => t('“---” for em-dashes; “--” for en-dashes'),
130 // Replace space_hyphens with em-dash.
131 $form['space_hyphens'] = array(
132 '#type' => 'checkbox',
133 '#title' => t('Replace stand-alone dashes (normal dashes between whitespace) em-dashes.'),
134 '#description' => t('" - " will turn into " — ".'),
135 '#default_value' => $settings['space_hyphens'],
138 // Remove widows settings.
139 $form['widont_enabled'] = array(
140 '#type' => 'checkbox',
141 '#title' => t('Remove widows'),
142 '#default_value' => $settings['widont_enabled'],
145 // Remove widows settings.
146 $form['hyphenate_shy'] = array(
147 '#type' => 'checkbox',
148 '#title' => t('Replace <code>=</code> with <code>&shy;</code>'),
149 '#description' => t('Words may be broken at the hyphenation points marked by “=”.'),
150 '#default_value' => $settings['hyphenate_shy'],
153 // Replace normal spaces with non-breaking spaces before "double punctuation
154 // marks". This is especially useful in french.
155 $form['space_to_nbsp'] = array(
156 '#type' => 'checkbox',
157 '#title' => t('Replace normal spaces with non-breaking spaces before "double punctuation marks" !marks.',
158 array('!marks' => '(<code>!?:;</code>)')),
159 '#description' => t('This is especially useful for french.'),
160 '#default_value' => $settings['space_to_nbsp'],
163 // Wrap caps settings.
164 $form['wrap_caps'] = array(
165 '#type' => 'checkbox',
166 '#title' => t('Wrap caps'),
167 '#default_value' => $settings['wrap_caps'],
170 // Wrap ampersand settings.
171 $form['wrap_ampersand'] = array(
172 '#type' => 'checkbox',
173 '#title' => t('Wrap ampersands'),
174 '#default_value' => $settings['wrap_ampersand'],
177 $form['wrap_abbr'] = array(
179 '#title' => t('Thin space in abbreviations'),
180 '#description' => t('Wraps abbreviations with !span and inserts space after the dots.', array('!span' => '<code><span class="abbr">…</span></code>')),
181 '#default_value' => $settings['wrap_abbr'],
183 0 => t('Do nothing'),
184 4 => t('Insert no space'),
185 1 => t('“U+202F“ Narrow no-break space'),
186 2 => t('“U+2009“ Thin space'),
187 3 => t('span with margin-left: 0.167em'),
191 $form['wrap_numbers'] = array(
193 '#title' => t('Digit grouping in numbers'),
194 '#description' => t('Wraps numbers with !span and inserts thin space for digit grouping.', array('!span' => '<code><span class="number">…</span></code>')),
195 '#default_value' => $settings['wrap_numbers'],
197 0 => t('Do nothing'),
198 1 => t('“U+202F“ Narrow no-break space'),
199 2 => t('“U+2009“ Thin space'),
200 3 => t('span with margin-left: 0.167em'),
201 4 => t('just wrap numbers'),
205 // Wrap initial quotes settings.
206 $form['wrap_initial_quotes'] = array(
207 '#type' => 'checkbox',
208 '#title' => t('Wrap quotation marks'),
209 '#default_value' => $settings['wrap_initial_quotes'],
212 // Ligature conversion settings.
213 $ligature_options = array();
214 foreach (UnicodeConversion::map('ligature') as $ascii => $unicode) {
215 $ligature_options[$ascii] = t('Convert <code>@ascii</code> to !unicode', array(
217 '!unicode' => $unicode,
221 $form['ligatures'] = array(
222 '#type' => 'checkboxes',
223 '#title' => t('Ligatures'),
224 '#options' => $ligature_options,
225 '#default_value' => $settings['ligatures'],
228 // Arrow conversion settings.
229 $arrow_options = array();
230 foreach (UnicodeConversion::map('arrow') as $ascii => $unicode) {
231 $arrow_options[$ascii] = t('Convert <code>@ascii</code> to !unicode', array(
232 '@ascii' => $this->unquote($ascii),
233 '!unicode' => $unicode,
238 $form['arrows'] = array(
239 '#type' => 'checkboxes',
240 '#title' => t('Arrows'),
241 '#options' => $arrow_options,
242 '#default_value' => $settings['arrows'],
245 // Fraction conversion settings.
246 $fraction_options = array();
247 foreach (UnicodeConversion::map('fraction') as $ascii => $unicode) {
248 $fraction_options[$ascii] = t('Convert <code>@ascii</code> to !unicode', array(
250 '!unicode' => $unicode,
255 $form['fractions'] = array(
256 '#type' => 'checkboxes',
257 '#title' => t('Fractions'),
258 '#options' => $fraction_options,
259 '#default_value' => $settings['fractions'],
262 // Quotes conversion settings.
263 $quotes_options = array();
264 foreach (UnicodeConversion::map('quotes') as $quotes => $unicode) {
265 $quotes_options[$quotes] = t('Convert <code>@ascii</code> to !unicode', array(
266 '@ascii' => $this->unquote($quotes),
267 '!unicode' => $unicode,
271 $form['quotes'] = array(
272 '#type' => 'checkboxes',
273 '#title' => t('Quotes'),
274 '#options' => $quotes_options,
275 '#default_value' => $settings['quotes'],
278 // Version Information Settings.
279 $version_strings = array();
280 $version_strings[] = t('SmartyPants PHP version: !version', array(
281 '!version' => \Drupal::l(SmartyPants::SMARTYPANTS_PHP_VERSION, Url::fromUri('http://www.michelf.com/projects/php-smartypants/')),
283 $version_strings[] = t('PHP Typogrify Version: !version', array(
284 '!version' => \Drupal::l(PHP_TYPOGRIFY_VERSION, Url::fromUri('http://blog.hamstu.com/')),
287 $form['info']['typogrify_status'] = [
288 '#theme' => 'item_list',
289 '#items' => $version_strings,
290 '#title' => t('Versions'),
299 public function setConfiguration(array $configuration) {
300 static::settingsSerialize($configuration['settings']);
301 parent::setConfiguration($configuration);
307 public function process($text, $langcode) {
308 $settings = $this->settings;
309 static::settingsUnserialize($settings);
310 $characters_to_convert = array();
313 if ($langcode == 'und') {
314 $language = \Drupal::languageManager()->getCurrentLanguage();
316 // @fixme, check language business for d8
317 $ctx['langcode'] = $language->language;
320 $ctx['langcode'] = $langcode;
323 // Build a list of ligatures to convert.
324 foreach (UnicodeConversion::map('ligature') as $ascii => $unicode) {
325 if (isset($settings['ligatures'][$ascii]) && $settings['ligatures'][$ascii]) {
326 $characters_to_convert[] = $ascii;
331 if ($settings['wrap_caps']) {
332 $text = Typogrify::caps($text);
335 // Build a list of arrows to convert.
336 foreach (UnicodeConversion::map('arrow') as $ascii => $unicode) {
337 $htmle = $this->unquote($ascii);
338 if ((isset($settings['arrows'][$ascii]) && $settings['arrows'][$ascii]) ||
339 (isset($settings['arrows'][$htmle]) && $settings['arrows'][$htmle])) {
340 $characters_to_convert[] = $ascii;
344 // Build a list of fractions to convert.
345 foreach (UnicodeConversion::map('fraction') as $ascii => $unicode) {
346 if (isset($settings['fractions'][$ascii]) && $settings['fractions'][$ascii]) {
347 $characters_to_convert[] = $ascii;
351 // Build a list of quotation marks to convert.
352 foreach (UnicodeConversion::map('quotes') as $ascii => $unicode) {
353 if (isset($settings['quotes'][$ascii]) && $settings['quotes'][$ascii]) {
354 $characters_to_convert[] = $ascii;
358 // Convert ligatures and arrows.
359 if (count($characters_to_convert) > 0) {
360 $text = UnicodeConversion::convertCharacters($text, $characters_to_convert);
364 if ($settings['wrap_ampersand']) {
365 $text = SmartyPants::smartAmpersand($text);
368 // Smartypants formatting.
369 if ($settings['smartypants_enabled']) {
370 $text = SmartyPants::process($text, $settings['smartypants_hyphens'], $ctx);
373 // Wrap abbreviations.
374 if ($settings['wrap_abbr'] > 0) {
375 $text = SmartyPants::smartAbbreviation($text, $settings['wrap_abbr']);
378 // Wrap huge numbers.
379 if ($settings['wrap_numbers'] > 0) {
380 $text = SmartyPants::smartNumbers($text, $settings['wrap_numbers']);
383 // Wrap initial quotes.
384 if ($settings['wrap_initial_quotes']) {
385 $text = Typogrify::initial_quotes($text);
388 // Wrap initial quotes.
389 if ($settings['hyphenate_shy']) {
390 $text = SmartyPants::hyphenate($text);
394 if ($settings['widont_enabled']) {
395 $text = Typogrify::widont($text);
398 // Replace normal spaces with non-breaking spaces before "double punctuation
399 // marks". This is especially useful in french.
400 if (isset($settings['space_to_nbsp']) && $settings['space_to_nbsp']) {
401 $text = SmartyPants::spaceToNbsp($text);
404 // Replace normal whitespace '-' whitespace with em-dash.
405 if (isset($settings['space_hyphens']) && $settings['space_hyphens']) {
406 $text = SmartyPants::spaceHyphens($text);
409 return new FilterProcessResult($text);
415 public function tips($long = FALSE) {
416 $settings = $this->settings;
419 $output = t('Typogrify.module brings the typographic refinements of Typogrify to Drupal.');
421 if ($settings['wrap_ampersand']) {
422 $output .= '<li>' . t('Wraps ampersands (the “&” character) with !span.', array('!span' => '<code><span class="amp">&</span></code>')) . '</li>';
424 if ($settings['widont_enabled']) {
425 $output .= '<li>' . t("Prevents single words from wrapping onto their own line using Shaun Inman's Widont technique.") . '</li>';
427 if ($settings['wrap_initial_quotes']) {
428 $output .= '<li>' . t("Converts straight quotation marks to typographer's quotation marks, using SmartyPants.");
429 $output .= '</li><li>' . t('Wraps initial quotation marks with !quote or !dquote.', array(
430 '!quote' => '<code><span class="quo"></span></code>',
431 '!dquote' => '<code><span class="dquo"></span></code>',
434 $output .= t('<li>Converts multiple hyphens to en dashes and em dashes (according to your preferences), using SmartyPants.</li>');
435 if ($settings['hyphenate_shy']) {
436 $output .= '<li>' . t('Words may be broken at the hyphenation points marked by “=”.') . '</li>';
438 if ($settings['wrap_abbr']) {
439 $output .= '<li>' . t('Wraps abbreviations as “e.g.” to !span and adds a thin space (1/6 em) after the dots.</li>', array('!span' => '<code><span class="abbr">e.g.</span></code>')) . '</li>';
441 if ($settings['wrap_numbers']) {
442 $output .= '<li>' . t('Wraps large numbers > 1 000 with !span and inserts thin space for digit grouping.', array('!span' => '<code><span class="number">…</span></code>')) . '</li>';
444 if ($settings['wrap_caps']) {
445 $output .= '<li>' . t('Wraps multiple capital letters with !span.', array('!span' => '<code><span class="caps">CAPS</span></code>')) . '</li>';
447 $output .= '<li>' . t('Adds a css style sheet that uses the <span> tags to substitute a showy ampersand in headlines, switch caps to small caps, and hang initial quotation marks.') . '</li>';
448 // Build a list of quotation marks to convert.
449 foreach (UnicodeConversion::map('quotes') as $ascii => $unicode) {
450 if ($settings['quotes'][$ascii]) {
451 $ascii_to_unicode .= t('Converts <code>!ascii</code> to !unicode', array(
453 '!unicode' => $unicode,
455 $output .= "<li>$ascii_to_unicode</li>\n";
461 $output = t('Typographic refinements will be added.');
468 * Helper function to unquote a string.
472 * @param string|array $text
473 * String or array of strings to be unquoted.
475 * @return string|array
476 * Original $text with simple '<' and '>' instead of HTML entities.
478 private function unquote($text) {
480 array('<', '>'),