Turn audio into a shareable video. forked from nypublicradio/audiogram

text-wrapper.js 3.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. var smartquotes = require("smartquotes").string;
  2. module.exports = function(theme) {
  3. return function(context, language, type) {
  4. // Do some typechecking
  5. var left = ifNumeric(theme[type + 'Left'], 0),
  6. right = ifNumeric(theme[type + 'Right'], theme.width),
  7. bottom = ifNumeric(theme[type + 'Bottom'], null),
  8. top = ifNumeric(theme[type + 'Top'], null);
  9. if (!language || language === 'None') {
  10. return;
  11. }
  12. calculate(language, type);
  13. function calculate (txt, type) {
  14. var lines = [[]],
  15. wrap_width = right - left,
  16. max_width = 0,
  17. words = smartquotes(txt + "").trim().replace(/\s\s+/g, " \n").split(/ /g),
  18. indent;
  19. if (type === 'caption') {
  20. // negative indentation for opening quotes
  21. indent = 20;
  22. }
  23. else if (type === 'citation') {
  24. // negative indentation for opening em dash
  25. indent = 50;
  26. }
  27. if (bottom === null && top === null) {
  28. top = 0;
  29. }
  30. context.font = theme[type + 'Font'];
  31. context.textBaseline = "top";
  32. context.textAlign = theme[type + 'Align'] || "center";
  33. // Check whether each word exceeds the width limit
  34. // Wrap onto next line as needed
  35. words.forEach(function(word,i){
  36. var width = context.measureText(lines[lines.length - 1].concat([word]).join(" ")).width;
  37. if (word[0] === "\n" || (lines[lines.length - 1].length && width > wrap_width)) {
  38. word = word.trim();
  39. lines.push([word]);
  40. width = context.measureText(word).width;
  41. } else {
  42. // automatically prepend em dash to citation
  43. word = (type === 'citation' && i === 0) ? '— ' + word : word;
  44. lines[lines.length - 1].push(word);
  45. }
  46. max_width = Math.max(max_width, width);
  47. });
  48. var totalHeight = lines.length * theme[type + 'LineHeight'] + (lines.length - 1) * theme[type + 'LineSpacing'];
  49. // horizontal alignment
  50. var x = theme[type + 'Align'] === "left" ? left : theme[type + 'Align'] === "right" ? right : (left + right) / 2;
  51. // Vertical alignment
  52. var y;
  53. if (top !== null && bottom !== null) {
  54. // Vertical center
  55. y = (bottom + top - totalHeight) / 2;
  56. } else if (bottom !== null) {
  57. // Vertical align bottom
  58. y = bottom - totalHeight;
  59. } else {
  60. // Vertical align top
  61. y = top;
  62. }
  63. // draw text
  64. context.fillStyle = theme[type + 'Color'];
  65. lines.forEach(function(line, i){
  66. if (/caption|citation/.test(type)) {
  67. if (i === 0 && /^“|^—/.test(line[0])) {
  68. context.fillText(line.join(" "), x, y + i * (theme[type + 'LineHeight'] + theme[type + 'LineSpacing']));
  69. }
  70. else {
  71. context.fillText(line.join(" "), (x + indent), y + i * (theme[type + 'LineHeight'] + theme[type + 'LineSpacing']));
  72. }
  73. }
  74. else { // you're a label
  75. context.fillText(line.join(" "), x, y + i * (theme[type + 'LineHeight'] + theme[type + 'LineSpacing']));
  76. }
  77. });
  78. }
  79. };
  80. }
  81. function ifNumeric(val, alt) {
  82. return (typeof val === "number" && !isNaN(val)) ? val : alt;
  83. }