--- /dev/null
+<?php
+
+namespace Drupal\typogrify\Plugin\Filter;
+
+use Drupal\typogrify\SmartyPants;
+use Drupal\typogrify\Typogrify;
+use Drupal\typogrify\UnicodeConversion;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\filter\FilterProcessResult;
+use Drupal\filter\Plugin\FilterBase;
+use Drupal\Core\Url;
+
+/**
+ * Provides a filter to restrict images to site.
+ *
+ * @Filter(
+ * id = "TypogrifyFilter",
+ * title = @Translation("Typogrify"),
+ * description = @Translation("Adds typographic refinements"),
+ * type = Drupal\filter\Plugin\FilterInterface::TYPE_TRANSFORM_IRREVERSIBLE,
+ * settings = {
+ * "smartypants_enabled" = 1,
+ * "smartypants_hyphens" = 3,
+ * "space_hyphens" = 0,
+ * "wrap_ampersand" = 1,
+ * "widont_enabled"= 1,
+ * "space_to_nbsp" = 1,
+ * "hyphenate_shy" = 0,
+ * "wrap_abbr" = 0,
+ * "wrap_caps" = 1,
+ * "wrap_initial_quotes" = 1,
+ * "wrap_numbers" = 0,
+ * "ligatures" = "a:0:{}",
+ * "arrows" = "a:0:{}",
+ * "fractions" = "a:0:{}",
+ * "quotes" = "a:0:{}",
+ * },
+ * weight = 10
+ * )
+ */
+class TypogrifyFilter extends FilterBase {
+
+ /**
+ * The keys in the settings array that are array-valued.
+ *
+ * @var array
+ */
+ protected static $arraySettingsKeys = array(
+ 'ligatures',
+ 'arrows',
+ 'fractions',
+ 'quotes',
+ );
+
+ /**
+ * Serialize array values.
+ *
+ * There must be a better way to do this, but it looks as though trying to
+ * save an array-valued plugin setting fails. Our solution is to serialize the
+ * settings before saving and unserialize them before using.
+ *
+ * Serialize $settings[$key] for each $key in $arraySettingsKeys.
+ *
+ * @param array &$settings
+ * The array of plugin settings.
+ *
+ * @see settingsUnserialize()
+ */
+ protected static function settingsSerialize(array &$settings) {
+ foreach (static::$arraySettingsKeys as $key) {
+ if (isset($settings[$key]) && is_array($settings[$key])) {
+ $settings[$key] = serialize(array_filter($settings[$key]));
+ }
+ }
+ }
+
+ /**
+ * Unserialize array values.
+ *
+ * Unserialize $settings[$key] for each $key in $arraySettingsKeys.
+ *
+ * @param array &$settings
+ * The array of plugin settings.
+ *
+ * @see settingsSerialize()
+ */
+ protected static function settingsUnserialize(array &$settings) {
+ foreach (static::$arraySettingsKeys as $key) {
+ if (isset($settings[$key]) && is_string($settings[$key])) {
+ $settings[$key] = unserialize($settings[$key]);
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function settingsForm(array $form, FormStateInterface $form_state) {
+ $settings = $this->settings;
+ static::settingsUnserialize($settings);
+
+ $form['help'] = array(
+ '#type' => 'markup',
+ '#value' => '<p>' . t('Enable the following typographic refinements:') . '</p>',
+ );
+
+ // Smartypants settings.
+ $form['smartypants_enabled'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Use typographers quotation marks and dashes (!smartylink)', array(
+ '!smartylink' => \Drupal::l('SmartyPants', Url::fromUri('http://daringfireball.net/projects/smartypants/')),
+ )),
+ '#default_value' => $settings['smartypants_enabled'],
+ );
+
+ // Smartypants hyphenation settings.
+ // Uses the same values as the parse attributes in the
+ // SmartyPants::process() function.
+ $form['smartypants_hyphens'] = array(
+ '#type' => 'select',
+ '#title' => t('Hyphenation settings for SmartyPants'),
+ '#default_value' => $settings['smartypants_hyphens'],
+ '#options' => array(
+ 1 => t('“--” for em-dashes; no en-dash support'),
+ 3 => t('“--” for em-dashes; “---” for en-dashes'),
+ 2 => t('“---” for em-dashes; “--” for en-dashes'),
+ ),
+ );
+
+ // Replace space_hyphens with em-dash.
+ $form['space_hyphens'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Replace stand-alone dashes (normal dashes between whitespace) em-dashes.'),
+ '#description' => t('" - " will turn into " — ".'),
+ '#default_value' => $settings['space_hyphens'],
+ );
+
+ // Remove widows settings.
+ $form['widont_enabled'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Remove widows'),
+ '#default_value' => $settings['widont_enabled'],
+ );
+
+ // Remove widows settings.
+ $form['hyphenate_shy'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Replace <code>=</code> with <code>&shy;</code>'),
+ '#description' => t('Words may be broken at the hyphenation points marked by “=”.'),
+ '#default_value' => $settings['hyphenate_shy'],
+ );
+
+ // Replace normal spaces with non-breaking spaces before "double punctuation
+ // marks". This is especially useful in french.
+ $form['space_to_nbsp'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Replace normal spaces with non-breaking spaces before "double punctuation marks" !marks.',
+ array('!marks' => '(<code>!?:;</code>)')),
+ '#description' => t('This is especially useful for french.'),
+ '#default_value' => $settings['space_to_nbsp'],
+ );
+
+ // Wrap caps settings.
+ $form['wrap_caps'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Wrap caps'),
+ '#default_value' => $settings['wrap_caps'],
+ );
+
+ // Wrap ampersand settings.
+ $form['wrap_ampersand'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Wrap ampersands'),
+ '#default_value' => $settings['wrap_ampersand'],
+ );
+
+ $form['wrap_abbr'] = array(
+ '#type' => 'select',
+ '#title' => t('Thin space in abbreviations'),
+ '#description' => t('Wraps abbreviations with !span and inserts space after the dots.', array('!span' => '<code><span class="abbr">…</span></code>')),
+ '#default_value' => $settings['wrap_abbr'],
+ '#options' => array(
+ 0 => t('Do nothing'),
+ 4 => t('Insert no space'),
+ 1 => t('“U+202F“ Narrow no-break space'),
+ 2 => t('“U+2009“ Thin space'),
+ 3 => t('span with margin-left: 0.167em'),
+ ),
+ );
+
+ $form['wrap_numbers'] = array(
+ '#type' => 'select',
+ '#title' => t('Digit grouping in numbers'),
+ '#description' => t('Wraps numbers with !span and inserts thin space for digit grouping.', array('!span' => '<code><span class="number">…</span></code>')),
+ '#default_value' => $settings['wrap_numbers'],
+ '#options' => array(
+ 0 => t('Do nothing'),
+ 1 => t('“U+202F“ Narrow no-break space'),
+ 2 => t('“U+2009“ Thin space'),
+ 3 => t('span with margin-left: 0.167em'),
+ 4 => t('just wrap numbers'),
+ ),
+ );
+
+ // Wrap initial quotes settings.
+ $form['wrap_initial_quotes'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Wrap quotation marks'),
+ '#default_value' => $settings['wrap_initial_quotes'],
+ );
+
+ // Ligature conversion settings.
+ $ligature_options = array();
+ foreach (UnicodeConversion::map('ligature') as $ascii => $unicode) {
+ $ligature_options[$ascii] = t('Convert <code>@ascii</code> to !unicode', array(
+ '@ascii' => $ascii,
+ '!unicode' => $unicode,
+ ));
+ }
+
+ $form['ligatures'] = array(
+ '#type' => 'checkboxes',
+ '#title' => t('Ligatures'),
+ '#options' => $ligature_options,
+ '#default_value' => $settings['ligatures'],
+ );
+
+ // Arrow conversion settings.
+ $arrow_options = array();
+ foreach (UnicodeConversion::map('arrow') as $ascii => $unicode) {
+ $arrow_options[$ascii] = t('Convert <code>@ascii</code> to !unicode', array(
+ '@ascii' => $this->unquote($ascii),
+ '!unicode' => $unicode,
+ ));
+
+ }
+
+ $form['arrows'] = array(
+ '#type' => 'checkboxes',
+ '#title' => t('Arrows'),
+ '#options' => $arrow_options,
+ '#default_value' => $settings['arrows'],
+ );
+
+ // Fraction conversion settings.
+ $fraction_options = array();
+ foreach (UnicodeConversion::map('fraction') as $ascii => $unicode) {
+ $fraction_options[$ascii] = t('Convert <code>@ascii</code> to !unicode', array(
+ '@ascii' => $ascii,
+ '!unicode' => $unicode,
+ ));
+
+ }
+
+ $form['fractions'] = array(
+ '#type' => 'checkboxes',
+ '#title' => t('Fractions'),
+ '#options' => $fraction_options,
+ '#default_value' => $settings['fractions'],
+ );
+
+ // Quotes conversion settings.
+ $quotes_options = array();
+ foreach (UnicodeConversion::map('quotes') as $quotes => $unicode) {
+ $quotes_options[$quotes] = t('Convert <code>@ascii</code> to !unicode', array(
+ '@ascii' => $this->unquote($quotes),
+ '!unicode' => $unicode,
+ ));
+ }
+
+ $form['quotes'] = array(
+ '#type' => 'checkboxes',
+ '#title' => t('Quotes'),
+ '#options' => $quotes_options,
+ '#default_value' => $settings['quotes'],
+ );
+
+ // Version Information Settings.
+ $version_strings = array();
+ $version_strings[] = t('SmartyPants PHP version: !version', array(
+ '!version' => \Drupal::l(SmartyPants::SMARTYPANTS_PHP_VERSION, Url::fromUri('http://www.michelf.com/projects/php-smartypants/')),
+ ));
+ $version_strings[] = t('PHP Typogrify Version: !version', array(
+ '!version' => \Drupal::l(PHP_TYPOGRIFY_VERSION, Url::fromUri('http://blog.hamstu.com/')),
+ ));
+
+ $form['info']['typogrify_status'] = [
+ '#theme' => 'item_list',
+ '#items' => $version_strings,
+ '#title' => t('Versions'),
+ ];
+
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setConfiguration(array $configuration) {
+ static::settingsSerialize($configuration['settings']);
+ parent::setConfiguration($configuration);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function process($text, $langcode) {
+ $settings = $this->settings;
+ static::settingsUnserialize($settings);
+ $characters_to_convert = array();
+ $ctx = array();
+
+ if ($langcode == 'und') {
+ $language = \Drupal::languageManager()->getCurrentLanguage();
+
+ // @fixme, check language business for d8
+ $ctx['langcode'] = $language->language;
+ }
+ else {
+ $ctx['langcode'] = $langcode;
+ }
+
+ // Build a list of ligatures to convert.
+ foreach (UnicodeConversion::map('ligature') as $ascii => $unicode) {
+ if (isset($settings['ligatures'][$ascii]) && $settings['ligatures'][$ascii]) {
+ $characters_to_convert[] = $ascii;
+ }
+ }
+
+ // Wrap caps.
+ if ($settings['wrap_caps']) {
+ $text = Typogrify::caps($text);
+ }
+
+ // Build a list of arrows to convert.
+ foreach (UnicodeConversion::map('arrow') as $ascii => $unicode) {
+ $htmle = $this->unquote($ascii);
+ if ((isset($settings['arrows'][$ascii]) && $settings['arrows'][$ascii]) ||
+ (isset($settings['arrows'][$htmle]) && $settings['arrows'][$htmle])) {
+ $characters_to_convert[] = $ascii;
+ }
+ }
+
+ // Build a list of fractions to convert.
+ foreach (UnicodeConversion::map('fraction') as $ascii => $unicode) {
+ if (isset($settings['fractions'][$ascii]) && $settings['fractions'][$ascii]) {
+ $characters_to_convert[] = $ascii;
+ }
+ }
+
+ // Build a list of quotation marks to convert.
+ foreach (UnicodeConversion::map('quotes') as $ascii => $unicode) {
+ if (isset($settings['quotes'][$ascii]) && $settings['quotes'][$ascii]) {
+ $characters_to_convert[] = $ascii;
+ }
+ }
+
+ // Convert ligatures and arrows.
+ if (count($characters_to_convert) > 0) {
+ $text = UnicodeConversion::convertCharacters($text, $characters_to_convert);
+ }
+
+ // Wrap ampersands.
+ if ($settings['wrap_ampersand']) {
+ $text = SmartyPants::smartAmpersand($text);
+ }
+
+ // Smartypants formatting.
+ if ($settings['smartypants_enabled']) {
+ $text = SmartyPants::process($text, $settings['smartypants_hyphens'], $ctx);
+ }
+
+ // Wrap abbreviations.
+ if ($settings['wrap_abbr'] > 0) {
+ $text = SmartyPants::smartAbbreviation($text, $settings['wrap_abbr']);
+ }
+
+ // Wrap huge numbers.
+ if ($settings['wrap_numbers'] > 0) {
+ $text = SmartyPants::smartNumbers($text, $settings['wrap_numbers']);
+ }
+
+ // Wrap initial quotes.
+ if ($settings['wrap_initial_quotes']) {
+ $text = Typogrify::initial_quotes($text);
+ }
+
+ // Wrap initial quotes.
+ if ($settings['hyphenate_shy']) {
+ $text = SmartyPants::hyphenate($text);
+ }
+
+ // Remove widows.
+ if ($settings['widont_enabled']) {
+ $text = Typogrify::widont($text);
+ }
+
+ // Replace normal spaces with non-breaking spaces before "double punctuation
+ // marks". This is especially useful in french.
+ if (isset($settings['space_to_nbsp']) && $settings['space_to_nbsp']) {
+ $text = SmartyPants::spaceToNbsp($text);
+ }
+
+ // Replace normal whitespace '-' whitespace with em-dash.
+ if (isset($settings['space_hyphens']) && $settings['space_hyphens']) {
+ $text = SmartyPants::spaceHyphens($text);
+ }
+
+ return new FilterProcessResult($text);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function tips($long = FALSE) {
+ $settings = $this->settings;
+
+ if ($long) {
+ $output = t('Typogrify.module brings the typographic refinements of Typogrify to Drupal.');
+ $output .= '<ul>';
+ if ($settings['wrap_ampersand']) {
+ $output .= '<li>' . t('Wraps ampersands (the “&” character) with !span.', array('!span' => '<code><span class="amp">&</span></code>')) . '</li>';
+ }
+ if ($settings['widont_enabled']) {
+ $output .= '<li>' . t("Prevents single words from wrapping onto their own line using Shaun Inman's Widont technique.") . '</li>';
+ }
+ if ($settings['wrap_initial_quotes']) {
+ $output .= '<li>' . t("Converts straight quotation marks to typographer's quotation marks, using SmartyPants.");
+ $output .= '</li><li>' . t('Wraps initial quotation marks with !quote or !dquote.', array(
+ '!quote' => '<code><span class="quo"></span></code>',
+ '!dquote' => '<code><span class="dquo"></span></code>',
+ )) . '</li>';
+ }
+ $output .= t('<li>Converts multiple hyphens to en dashes and em dashes (according to your preferences), using SmartyPants.</li>');
+ if ($settings['hyphenate_shy']) {
+ $output .= '<li>' . t('Words may be broken at the hyphenation points marked by “=”.') . '</li>';
+ }
+ if ($settings['wrap_abbr']) {
+ $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>';
+ }
+ if ($settings['wrap_numbers']) {
+ $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>';
+ }
+ if ($settings['wrap_caps']) {
+ $output .= '<li>' . t('Wraps multiple capital letters with !span.', array('!span' => '<code><span class="caps">CAPS</span></code>')) . '</li>';
+ }
+ $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>';
+ // Build a list of quotation marks to convert.
+ foreach (UnicodeConversion::map('quotes') as $ascii => $unicode) {
+ if ($settings['quotes'][$ascii]) {
+ $ascii_to_unicode .= t('Converts <code>!ascii</code> to !unicode', array(
+ '!ascii' => $ascii,
+ '!unicode' => $unicode,
+ ));
+ $output .= "<li>$ascii_to_unicode</li>\n";
+ }
+ }
+ $output .= '</ul>';
+ }
+ else {
+ $output = t('Typographic refinements will be added.');
+ }
+
+ return $output;
+ }
+
+ /**
+ * Helper function to unquote a string.
+ *
+ * Unquotes a string.
+ *
+ * @param string|array $text
+ * String or array of strings to be unquoted.
+ *
+ * @return string|array
+ * Original $text with simple '<' and '>' instead of HTML entities.
+ */
+ private function unquote($text) {
+ $text = str_replace(
+ array('<', '>'),
+ array('<', '>'),
+ $text);
+
+ return $text;
+ }
+
+}