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

text-wrapper.js 3.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  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. // save caption height for measuring citation top
  50. if (type === 'caption') {
  51. theme.captionTotalHeight = totalHeight;
  52. }
  53. // horizontal alignment
  54. var x = theme[type + 'Align'] === "left" ? left : theme[type + 'Align'] === "right" ? right : (left + right) / 2;
  55. // Vertical alignment
  56. var y;
  57. if (top !== null && bottom !== null) {
  58. // Vertical center
  59. y = (bottom + top - totalHeight) / 2;
  60. } else if (bottom !== null) {
  61. // Vertical align bottom
  62. y = bottom - totalHeight;
  63. } else {
  64. // Vertical align top
  65. if (type === 'citation' && theme.captionTotalHeight) {
  66. y = theme.captionTop + theme.captionTotalHeight + theme[type + 'TopMargin'];
  67. }
  68. else {
  69. y = top;
  70. }
  71. }
  72. // draw text
  73. context.fillStyle = theme[type + 'Color'];
  74. lines.forEach(function(line, i){
  75. if (/caption|citation/.test(type)) {
  76. if (i === 0 && /^“|^—/.test(line[0])) {
  77. context.fillText(line.join(" "), x, y + i * (theme[type + 'LineHeight'] + theme[type + 'LineSpacing']));
  78. }
  79. else {
  80. context.fillText(line.join(" "), (x + indent), y + i * (theme[type + 'LineHeight'] + theme[type + 'LineSpacing']));
  81. }
  82. }
  83. else { // you're a label
  84. context.fillText(line.join(" "), x, y + i * (theme[type + 'LineHeight'] + theme[type + 'LineSpacing']));
  85. }
  86. });
  87. }
  88. };
  89. }
  90. function ifNumeric(val, alt) {
  91. return (typeof val === "number" && !isNaN(val)) ? val : alt;
  92. }