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

text-wrapper.js 3.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  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 = 20;
  19. if (bottom === null && top === null) {
  20. top = 0;
  21. }
  22. context.font = theme[type + 'Font'];
  23. context.textBaseline = "top";
  24. context.textAlign = theme[type + 'Align'] || "center";
  25. // Check whether each word exceeds the width limit
  26. // Wrap onto next line as needed
  27. words.forEach(function(word,i){
  28. var width = context.measureText(lines[lines.length - 1].concat([word]).join(" ")).width;
  29. if (word[0] === "\n" || (lines[lines.length - 1].length && width > wrap_width)) {
  30. word = word.trim();
  31. lines.push([word]);
  32. width = context.measureText(word).width;
  33. } else {
  34. lines[lines.length - 1].push(word);
  35. }
  36. max_width = Math.max(max_width, width);
  37. });
  38. var totalHeight = lines.length * theme[type + 'LineHeight'] + (lines.length - 1) * theme[type + 'LineSpacing'];
  39. // save caption height for measuring citation top
  40. if (type === 'caption') {
  41. theme.captionTotalHeight = totalHeight;
  42. }
  43. // horizontal alignment
  44. var x = theme[type + 'Align'] === "left" ? left : theme[type + 'Align'] === "right" ? right : (left + right) / 2;
  45. // Vertical alignment
  46. var y;
  47. if (top !== null && bottom !== null) {
  48. // Vertical center
  49. y = (bottom + top - totalHeight) / 2;
  50. } else if (bottom !== null) {
  51. // Vertical align bottom
  52. y = bottom - totalHeight;
  53. } else {
  54. // Vertical align top
  55. if (type === 'citation' && theme.captionTotalHeight) {
  56. y = theme.captionTop + theme.captionTotalHeight + theme[type + 'TopMargin'];
  57. }
  58. else {
  59. y = top;
  60. }
  61. }
  62. // draw text
  63. context.fillStyle = theme[type + 'Color'];
  64. lines.forEach(function(line, i){
  65. if (/caption|citation/.test(type)) {
  66. if (i === 0 && /^“/.test(line[0])) {
  67. context.fillText(line.join(" "), x, y + i * (theme[type + 'LineHeight'] + theme[type + 'LineSpacing']));
  68. }
  69. else {
  70. context.fillText(line.join(" "), (x + indent), y + i * (theme[type + 'LineHeight'] + theme[type + 'LineSpacing']));
  71. }
  72. }
  73. else { // you're a label
  74. context.fillText(line.join(" "), x, y + i * (theme[type + 'LineHeight'] + theme[type + 'LineSpacing']));
  75. }
  76. });
  77. }
  78. };
  79. }
  80. function ifNumeric(val, alt) {
  81. return (typeof val === "number" && !isNaN(val)) ? val : alt;
  82. }