Javascript / Jquery делает текст идеально подходящим для заданных размеров div с разрывом строки (при необходимости) - PullRequest
0 голосов
/ 15 января 2019

Мне сложно разобраться: у меня есть эти функции, которые я использую для того, чтобы вписать текст в одну строку в заданные размеры.

function getFontSize(width, height, text, font, callback) {
    var n = 100;
    var ctxfont = n + 'px ' + font;
    var result = measureTextHeight(ctxfont, text);
    while (result.width > width || result.height > height) {
        n--;
        var ctxfont = n + 'px ' + font;
        var result = measureTextHeight(ctxfont, text);
    }
    callback({
        'width': result.width,
        'height': result.height,
        'size': n
    });
}

function measureTextHeight(ctxFont, text) {
    var width = 1500;
    var height = 500;

    var canvas = document.createElement("canvas");
    canvas.width = width;
    canvas.height = height;
    var ctx = canvas.getContext("2d");
    ctx.save();
    ctx.font = ctxFont;
    ctx.clearRect(0, 0, width, height);
    ctx.fillText(text, parseInt(width * 0.1, 10), parseInt(height / 2, 10));
    ctx.restore();
    document.body.appendChild(canvas);
    var data = ctx.getImageData(0, 0, width, height).data;


    var topMost = false;
    var bottomMost = false;
    var leftMost = false;
    var rightMost = false;
    for (var x = 0; x < width; x++) {
        for (var y = 0; (y < height) && (!leftMost); y++) {
            //console.log("x: %s y: %s index: %s", x,y, getAlphaIndexForCoordinates(x,y,width,height).toString() );
            if (data[getAlphaIndexForCoordinates(x, y, width, height)] != 0) {
                leftMost = x;
            }
        }
    }
    for (var y = 0; y < height; y++) {
        for (var x = 0; (x < width) && (!topMost); x++) {
            //console.log("x: %s y: %s index: %s", x,y, getAlphaIndexForCoordinates(x,y,width,height).toString() );
            if (data[getAlphaIndexForCoordinates(x, y, width, height)] != 0) {
                topMost = y;
            }
        }
    }
    for (var x = width - 1; x >= 0; x--) {
        for (var y = height - 1; (y >= 0) && (!rightMost); y--) {
            //console.log("x: %s y: %s index: %s", x,y, getAlphaIndexForCoordinates(x,y,width,height).toString() );
            if (data[getAlphaIndexForCoordinates(x, y, width, height)] != 0) {
                rightMost = x;
            }
        }
    }
    for (var y = height - 1; y >= 0; y--) {
        for (var x = width - 1; (x >= 0) && (!bottomMost); x--) {
            //console.log("x: %s y: %s index: %s", x,y, getAlphaIndexForCoordinates(x,y,width,height).toString() );
            if (data[getAlphaIndexForCoordinates(x, y, width, height)] != 0) {
                bottomMost = y;
            }
        }
    }
    canvas.remove();
    return ({
        width: (rightMost - leftMost) + 1
        , height: (bottomMost - topMost) + 1
    });
}

function getAlphaIndexForCoordinates(x, y, width, height) {
    return (((width * 4 * y) + 4 * x) + 3);
}

Я передаю нужные размеры и шрифт в функцию getFontSize, и она возвращает реальную ширину и высоту текста, а также размер шрифта, необходимый для его выполнения. Таким образом, я могу нарисовать текст на холсте, используя функцию ctx.filltext (), насколько это возможно, и выровнять его по центру, основываясь на его ширине и высоте. Я не уверен, что это самый эффективный способ достижения желаемого результата, но он работает.

То, что я хочу сделать сейчас, это вместо того, чтобы сделать его однострочным, я могу установить заданную ширину и высоту для div, а затем, давая текст, он будет идеально соответствовать этим размерам, добавляя разрывы строк, если это необходимо, и по-прежнему возвращать реальная ширина и высота текста, чтобы я мог правильно нарисовать его на холсте, как эта скрипка: http://jsfiddle.net/vkgjrd3e/, хотя в скрипте у него все еще есть место в нижней части div.

То, чего я пытаюсь добиться, - это наилучшее соответствие текста, учитывая его шрифт и размеры контейнера; добавление разрывов строк при необходимости.

1 Ответ

0 голосов
/ 01 февраля 2019

Возможно, проще всего использовать мощь CSS, а не пытаться делать это самостоятельно.

Вы можете создать фиктивный элемент div, добавить к нему и увеличивать размер шрифта до тех пор, пока он больше не будет соответствовать.
Размер шрифта до этого был правильным.

Затем вы можете просмотреть текстовое содержимое этого , используя объект Range , чтобы получить разрывы строк, которые генерирует CSS. Range API предоставляет удобный метод getBoundingClientRect , позволяющий нам найти, где находится курсор. Нам просто нужно запомнить последнюю позицию y, и когда она меняется, мы знаем, что символ раньше был последним в строке.

Однако у этой техники есть некоторые недостатки.

  • В DOM несколько пробелов сжимаются в один. CanvasContext API не имеет такого поведения, поэтому мы должны избавиться от них перед синтаксическим анализом текста.

  • Меры сделаны на основе ограничивающих рамок контейнеров, это означает, что он не проверяет окрашенные пиксели, поэтому некоторые символы могут быть переполнены (например, Zalgo͚̠͓ͣ̔͐̽), а некоторые другие не подходят идеально в поле (в зависимости от подъема и спуска каждого персонажа).

  • ... возможно, другие.

function getBestFontSize(text, width, height, userstyles) {
  if(!text) return null;
  
  const cont = document.createElement('div');
  cont.classList.add('best-font-size-tester');
  const style = cont.style;

  if(typeof width === 'number') width += 'px';
  if(typeof height === 'number') height += 'px';
  Object.assign(style, {width, height}, userstyles);

  const span = document.createElement('span');
  span.textContent = text;
  cont.appendChild(span);
  document.body.appendChild(cont);
  
  let size = 0;
  
  const max = cont.getBoundingClientRect();
  while(true) {
    style.fontSize = size + 'px';
    let rect = span.getBoundingClientRect();
    if(rect.bottom > max.bottom || rect.right > max.right) {
      // overflown
      size -= 1; // the correct size was the one before
      break;
    }
    size++;
  }
  if(size === 0) {
    // even at 0 it doesn't fit...
    return null;
  }
  // now we'll get the line breaks by walking through our text content
  style.fontSize = size + 'px';
  const lines = getLineBreaks(span.childNodes[0], max.top);
  // cleanup
  document.body.removeChild(cont);

  return {
    fontSize: size,
    lines: lines
  };
}

function getLineBreaks(node, contTop) {
  if(!node) return [];
  const range = document.createRange();
  const lines = [];
  range.setStart(node, 0);
  let prevBottom = range.getBoundingClientRect().bottom;
  let str = node.textContent;
  let current = 1;
  let lastFound = 0;
  let bottom = 0;
  while(current <= str.length) {
    range.setStart(node, current);
    if(current < str.length -1)
      range.setEnd(node, current+1);
    bottom = range.getBoundingClientRect().bottom;
    if(bottom > prevBottom) {
      lines.push({
        y: prevBottom - (contTop || 0),
        text: str.substr(lastFound , current - lastFound)
      });
      prevBottom = bottom;
      lastFound = current;
    }
    current++;
  }
  // push the last line
  lines.push({
    y: bottom - (contTop || 0),
    text: str.substr(lastFound)
  });

  return lines;
}

const ctx = canvas.getContext('2d');
ctx.textBaseline = 'bottom';
txt_area.oninput = e => {
  const input = txt_area.value
    .replace(/(\s)(?=\1)/g, ''); // remove all double spaces
  ctx.setTransform(1,0,0,1,0,0);
  ctx.clearRect(0,0,canvas.width,canvas.height);
  ctx.translate(19.5,19.5);
  ctx.strokeRect(0,0,100,100);

  if(!input.length) return;
  const bestFit = getBestFontSize(input, 100, 100, {
    fontFamily: 'sans-serif',
    fontWeight: '600',
    textAlign: 'center'
  });
  // apply thesame options we passed
  ctx.font = '600 ' + bestFit.fontSize + 'px sans-serif';
  ctx.textAlign = 'center';
  // translate again because text-align: center
  ctx.translate(50.5,0);
  bestFit.lines.forEach(({text, y}) => ctx.fillText(text, 0, y));
};
txt_area.oninput();
.best-font-size-tester {
  border: 1px solid;
  position: absolute;
  overflow: visible;
  opacity: 0;
  z-index: -1;
  pointer-events: none;
}
<textarea id="txt_area">This is an example text to fit the div even with line breaks</textarea>
<canvas id="canvas"></canvas>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...