Skip to content

Prism on a CDN

This is something I didn't know about: Prism, the syntax highlighter I use, is available from a few free CDN's, so I don't have to host it. Cool!

A programmable color scheme

I'm not a designer, and I've never been happy with the color scheme for kavanot.name, so I decided to try something new. I use scssphp with scssphp server (and yes, you need both) to generate the CSS, and it allows variables, so I can do:


$hue = 250;
$saturation = '40%';
$textcolor: hsl($hue, $saturation, 10%);
$displaytextcolor: hsl($hue, $saturation, 40%);
$backcolor: hsl($hue, $saturation, 95%);
$bordercolor: hsl($hue, $saturation, 90%);
$deepbackcolor: hsl($hue, $saturation, 85%);

to make all the colors a variation on one single color. And I like that. But what color? Fortunately, scssphp allows for importing variables, so my `styles.php` includes:


setcookie('hue', $_GET['hue'], time()+12*60*60*24*30, '/'); // one year expiration
setcookie('saturation', $_GET['saturation'], time()+12*60*60*24*30, '/'); // one year expiration

$scss = new Compiler();
$scss->setVariables(array(
	'hue' => intval($_GET['hue']),
	'saturation' => intval($_GET['saturation']).'%',
));

instead of the in-file $hue = 250; $saturation = '40%';. Now I can include those values in the URL for the stylesheet itself,
and the cookies mean that I don't have to write it each time.

To get the query variables to the stylesheet, I have to pass them from the main page:


	<link rel="stylesheet" id=mainstylesheet type="text/css" href="/css/style.php/style.scss?hue=<?=$_GET['hue']?>&saturation=<?=$_GET['saturation']?>" />

(I am ignoring the input sanitization you need to avoid XSS. intval is your friend here!)

But retyping http://kavanot.name?hue=250&saturation=40 with minor variations is a pain. Can I make it interactive? Yes, with a Javascript
color picker. <input type=color /> is supported in all the major browsers, so I can do:


var colorPicker = $('input[type=color]');
let baseHSL = getBaseHSL(); // get the body color as [hue, saturation, lightness]
baseHSL[2] = 50; // start with a color that can be easily seen, 50% lightness
colorPicker.val(hsl2hex(...baseHSL)); // set the initial value

const stylesheet = $('#mainstylesheet')[0];

colorPicker.on('input', function(){
	let [h, s] = hex2hsl(this.value);
	// change the stylesheet href and the styles change on the fly!
	stylesheet.href = stylesheet.href.replace(/hue=\d+/, `hue=${h}`).replace(/saturation=\d+/, `saturation=${s}`);
});

function getBaseHSL(){
	// getComputedStyle.color is rgb(r,g,b)
	let [, r, g, b] = /(\d+),\s*(\d+),\s*(\d+)/.exec(window.getComputedStyle(document.body).color);
	return rgb2hsl (r, g, b);
}

Now I need the functions rgb2hsl, hex2hsl and hsl2hex to convert between the various color spaces. Luckily,
Jon Kantner has done the work for me.


function hex2hsl(hex){
	let [, r, g, b] = /#(..)(..)(..)/.exec(hex);
	return rgb2hsl (+('0x'+r), +('0x'+g), +('0x'+b));
}
	
function rgb2hsl (r, g, b){
	// convert to fractions
	r = r/255;
	b = b/255;
	g = g/255;

	// find greatest and smallest channel values
	const cmin = Math.min(r,g,b);
	const cmax = Math.max(r,g,b);
	const delta = cmax - cmin;
	let h;

	// calculate hue
	if (delta == 0){ // no difference
		h = 0;
	}else if (cmax == r){ // red is max
		h = ((g - b) / delta) % 6;
	}else if (cmax == g){
		h = (b - r) / delta + 2; // green is max
	}else{
		h = (r - g) / delta + 4; // blue is max
	}
	h = Math.round(h * 60);
	if (h < 0) h += 360;

	// calculate lightness
	let l = (cmax + cmin) / 2;

	// calculate saturation
	let s = delta == 0 ? 0 : delta / (1 - Math.abs(2 * l - 1));

	// make s and l percents
	return [h, Math.round(s*100), Math.round(l*100)];
}

function hsl2hex(h, s, l){
	let c = (1 - Math.abs(2 * l/100 - 1)) * s/100;
	let x = c * (1 - Math.abs((h / 60) % 2 - 1));
	let m = l/100 - c/2;
	let r, g, b;
	
	if (0 <= h && h < 60) {
		r = c; g = x; b = 0;
	} else if (60 <= h && h < 120) {
		r = x; g = c; b = 0;
	} else if (120 <= h && h < 180) {
		r = 0; g = c; b = x;
	} else if (180 <= h && h < 240) {
		r = 0; g = x; b = c;
	} else if (240 <= h && h < 300) {
		r = x; g = 0; b = c;
	} else if (300 <= h && h < 360) {
		r = c; g = 0; b = x;
	}

	r = Math.round((r + m) * 255);
	g = Math.round((g + m) * 255);
	b = Math.round((b + m) * 255);
	
	return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
}

function toHex(d) {
	d = d.toString(16);
	if (d.length == 1) d = `0${d}`;
	return d.toUpperCase();
}

and now I can play with colors to my heart's content!

Implementing the History interface

The way browser history is implemented, with the History interface, is useful for other things than site navigation. The same idea, basically an extended stack that allows going back and forward, is the same algorithm used for undo/redo, and command line history.

It would be nice to be able to do const h = new History() and h.pushState(someData) with arbitrary states, but the browser won't let you construct a new instance of History. It's only accessible from window.history.

So I re-defined History to allow this.

See the details on the github site.

Moving bililiteRange into the modern age.

I haven't updated bililiteRange for years, but it's time to drop support for Internet Explorer and use modern constructs like Promise and input events. It should make things more simple. From here on in, I will only support Chrome, Firefox and Edge (the chromium-based one; I don't have Edge Legacy. I'm also working on moving all the documentation into the git repositories themselves, as github pages (see github.bililite.com, which is an alias for dwachss.github.io).

I've updated jquery.status.js, jquery.keymap.js, and timer events. The documentation for the timer events and status are done.

Extending Parsedown: attributes

Markdown Extra (including Parsedown Extra) allows for attributes to be applied to certain elements: headers, fenced code blocks, links, and images. I'd like to be able to apply them to any element. I'm going to use the syntax of Python Markdown attributes, but the attribute lists go before the elements. For block level elements, they go on their own line before the element.

My attribute lists start with {: (not just {) and end with }. Anything that would be legal in HTML (as is fine, since I didn't write my own parser. I just used DOMDocument. There are three special cases:

  • .foo is changed to class="foo". Note that this is different from the Python code, which appends class names that start with .. Repeated attribute names in actual HTML are ignored, so to use two classes, use class="foo bar", not .foo .bar.
  • #foo is changed to id="foo".
  • Two letters alone are changed to lang=xx, since I use that attribute so much.

Continue reading ›

Extending Parsedown: Block elements

Continuing work on extending Parsedown.

See the actual code.

Adding block-level elements is not much different from adding inline elements. Kavanot.name originally used <footer> elements to indicate the source of a block quote:

<blockquote>
    Do or do not. There is no try.
  </blockquote>
  <footer>Yoda</footer>
</blockquote>

While marking blockquotes this way is now acceptable, for a long time it wasn't, and the recommended way was with <figure> and <figcaption>. KavanotParsedown uses the latter model:

<figure>
  <blockquote>
    Do or do not. There is no try.
  <figcaption>Yoda</figcaption>
<figure>

But I start with creating a <footer> and then modifying the DOM. So I want to have a block element that I will indicate with "--" at the start of the line.

function __construct(){
  $this->BlockTypes['-'][] = 'Source'; // only line needed to indicate a block level element
  // ... rest of the constructor
}

protected function blockSource($Line, $Block = null){
  if (preg_match('/^--[ ]*(.+)/', $Line['text'], $matches)) {
    return array(
      'element' => array(
        'name' => 'footer',
        'handler' => array(
          'function' => 'lineElements',
          'argument' => $matches[1],
          'destination' => 'elements'
        ),
        'attributes' => array('class' => 'source') // for styling, add a class automatically
      )
    );
  }
}

so

>Do or do not. There is no try.
--Yoda

becomes

<blockquote>
    Do or do not. There is no try.
  <footer class="source" >Yoda</footer>
</blockquote>

I realized that I might want to add an attribution to an image as well, without it being in a <blockquote>, as

<p>
  <img src=/blog/blogfiles/pdf/smiley.png alt="Smile!"/>
  <footer class="source" >Some file I found on the web</footer>
</p>

But as it stands,

[Smile!](/blog/blogfiles/pdf/smiley.png)
--Some file I found on the web

doesn't work; the <p> ends before the <footer> starts:

<p>
  <img src=/blog/blogfiles/pdf/smiley.png alt="Smile!"/>
</p>
<footer class="source" >Some file I found on the web</footer>

so we need to check that the previous block wasn't a paragraph. If it was, then parse this line and add it to the paragraph as an internal element:

protected function blockSource($Line, $Block = null){
  if (preg_match('/^--[ ]*(.+)/', $Line['text'], $matches)) {
    if ($Block && $Block['type'] === 'Paragraph'){
      $Block['element']['handler']['argument'] .= "\n".$this->element($this->blockSource($Line)['element']);
      return $Block;
    }
    return array(
      'element' => array(
        'name' => 'footer',
        'handler' => array(
          'function' => 'lineElements',
          'argument' => $matches[1],
          'destination' => 'elements'
        ),
        'attributes' => array('class' => 'source') // for styling, add a class automatically
      )
    );
  }
}

and now it works, except that the footer is a child of the <p> instead of the <blockquote>. We'll have to fix that.

Extending Parsedown: Inline elements

Extending Parsedown involves adding elements to the $InlineTypes and $BlockTypes arrays, then adding methods to handle them.

See the actual code.

Italics

I use <i> a lot, to indicate transliterated words. So I use could use "/" to indicate that:
/Shabbat/ is a Hebrew word becomes <i>Shabbat</i> is a Hebrew word. To do that:
do

class myParsedown extends Parsedown{
  function __construct(){
    $this->InlineTypes['/'] []= 'Italic';
    // after adding all the new inline types, create the list of characters
    $this->inlineMarkerList = implode ('', array_keys($this->InlineTypes));
    // allow the character to be escaped by '\'
    $this->specialCharacters []= '/';
  }

  protected function inlineItalic($excerpt){
    if (preg_match('#^/(.+?)/#', $excerpt['text'], $matches)) {
      return array(
        'extent' => strlen($matches[0]), 
        'element' => array(
          'name' => 'i',
          'handler' => array(
            'function' => 'lineElements',
            'argument' => $matches[1],
            'destination' => 'elements'
          )
        )
      );
    }
}

Now, my transliterated words are almost always Hebrew, so I can automatically add the lang=he attribute:


  protected function inlineItalic($excerpt){
    if (preg_match('#^/(.+?)/#', $excerpt['text'], $matches)) {
      return array(
        'extent' => strlen($matches[0]), 
        'element' => array(
          'name' => 'i',
          'handler' => array(
            'function' => 'lineElements',
            'argument' => $matches[1],
            'destination' => 'elements'
          ),
          'attributes' => array('lang' => 'he') // Add attributes
        )
      );
    }
}

and now /Shabbat/ is a Hebrew word becomes <i lang=he>Shabbat</i> is a Hebrew word.

Cite

I also use the <cite>. I'm running out of single characters to indicate elements, so I'm going to redefine "-". I don't need two different markers for <em>.

  function __construct(){
    $this->InlineTypes['_'] = ['Cite']; // redefinition; I am replacing the old array (which was ['Emphasis'])
    // ... rest of the constructor as above
  }

  protected function inlineCite($excerpt){
    if (preg_match('#^_(.+?)_#', $excerpt['text'], $matches)) {
      return array(
        'extent' => strlen($matches[0]), 
        'element' => array(
          'name' => 'cite',
          'handler' => array(
            'function' => 'lineElements',
            'argument' => $matches[1],
            'destination' => 'elements'
          )
        )
      );
    }

And now _A Tale of Two Cities_ becomes <cite>A Tale of Two Cities</cite>

String Replacement in PHP

Working with Parsedown, I want to string manipulation but only in certain parts. For instance, on text not in HTML tags or not in quotes. The right way to do that is with a real parser. The easy way is by removing the unwanted strings, replacing them with a marker that won't come up in normal text, doing the manipulation, then replacing the markers (it is the replacement step that requires "a marker that won't come up in normal text"; you don't want to replace text that was present in the original).

I would use a marker that can't be typed but still is legal HTML; turns out that U+FFFC (OBJECT REPLACEMENT CHARACTER, ) is perfect for that. So I made a pair of functions, `StringReplace\remove` and `StringReplace\restore` to make that easy.

StringReplace\remove ($re, $target)
Any string that matches the regular expression $re in $target is replaced by a numbered marker, "{number}". The new string is returned. So for instance,

$rawtext = StringReplace\remove ('#</?[^>]*>#', $html);

will remove tags.

StringReplace\restore ($target)
Returns a string with the markers replaced by their original versions.

The code

namespace StringReplace;

define ('OBJECT_REPLACEMENT_CHARACTER', '');
define ('RE_REPLACEMENT', '/'.OBJECT_REPLACEMENT_CHARACTER.'(\d+)'.OBJECT_REPLACEMENT_CHARACTER.'/');

$strings = array();

$remover = function ($matches){
  global $strings;
  $strings []= $matches[0];
  return OBJECT_REPLACEMENT_CHARACTER.count($strings).OBJECT_REPLACEMENT_CHARACTER;
};

$replacer = function ($matches){
  global $strings;
  return $strings[$matches[1]-1];
};

function remove ($re, $target){
  global $remover;
  return preg_replace_callback ($re, $remover, $target);
}

function restore ($target){
  global $replacer;
  return preg_replace_callback (RE_REPLACEMENT, $replacer, $target);
}

Extending Parsedown

I've been spending all my intellectual free time on working on my Kavanot site, so I haven't been doing any independent programming. But that site uses raw HTML, which is a pain to type. So I decided to start using Markdown to make writing easier. After a little trial and error, I decided to use Parsedown with Parsedown Extra.

See the code.

Continue reading ›

H&R Block Updating Errors

(It's been almost a year since I've posted. My intellectual life is busy with other things)

I use H&R Block software to prepare my taxes and have been generally very happy, but this year it would not update the program after January 1. Using the automatic update downloaded the updater but it silently failed. Manually downloading from http://www.hrblock.com/tax-software/updates-back-editions/federal-windows.html then running that would run WinZip to unpack it, then silently fail. The tech support at H&R Block was useless.

Some Google-ing led to this solution:

  1. Run the update installer from the hrblock.com site as though it was going to work
  2. Wait for it to fail
  3. Find the unpacked downloaded installer:
    1. Windows-R key then type %temp% to open the temporary download folder
    2. Find the most recent folder (it will have a GUID-type name, like {deaf-face-1234}. Open it
    3. There should be an msp file like H&R Block Deluxe 2016 Update.msp
  4. Double click that to run it
  5. Run H&R Block

That's all it took! Obviously the unpacking worked correctly, so I don't know why the program wouldn't run the actual installer.