谷歌表格。如何获取实际范围大小(以像素为单位)

Max*_*rov 6 javascript google-sheets google-apps-script

我的脚本将选定的范围转换为图像,请参阅。它首先创建一个公共 PDF URL,然后将其转换为 PNG。

\n

在此输入图像描述

\n

它适用于小范围(10-20 行),并创建包含图像、图表、迷你图和格式的镜头。

\n

问题在于大范围(100-1000 行)。它们包含未知大小的边框,我无法计算它。

\n

在此输入图像描述

\n

粗边框会使行更高,因此图像不适合。

\n

在此输入图像描述

\n

如果我们没有边框或边框较薄,则实际图像尺寸会比计算的尺寸小一些。这会在图像下方创建一个空白区域。

\n

我的代码示例用于获取范围大小(以像素为单位):

\n
  // get row height in pixels\n  var h = 0;\n  for (var i = rownum; i <= rownum2; i++) {\n    if (i <= options.measure_limit) {\n      size = sheet.getRowHeight(i);\n    }\n    h += size\n    /** manual correction */\n    if (size === 2) {\n      h-=1;\n    } else {\n      // h -= 0.42; /** TODO \xe2\x86\x92 test the range to make it fit any range */\n    }\n    \n    if ((i % 50) === 0 &&  i <= options.measure_limit) {\n      file.toast(\n        \'Done \' + i + \' rows of \' + rownum2,\n        \'\xe2\x86\x95Measuring height...\');\n    }\n  }\n  if (i > options.measure_limit) {\n    file.toast(\n      \'Estimation: all other rows are the same size\',\n      \'\xe2\x86\x95Measuring height...\');\n  }\n
Run Code Online (Sandbox Code Playgroud)\n

如您所见,我必须循环所有行,这是效率极低的。我很高兴听到您对代码优化的想法。现在它循环前 150 行,接下来假设所有其他行具有相同的高度。

\n

示例情况

\n

“小”范围是您可以在屏幕上看到的范围。“大”范围有 100 多行,因此不适合普通屏幕。当我创建屏幕截图时,我测试了所有可能的范围大小。

\n

Case1 - 无边框或薄边框

\n

如果我选择一个大范围,我会得到图像,并看到它的底部有一个空白。这意味着图像的实际大小比我通过调用从脚本中获得的大小略小sheet.getRowHeight(i)

\n

Case1 - 粗边框

\n

如果我选择一个大范围,我会得到图像,并且看到并非我选择的所有行都在该图像上。该范围底部的一些行丢失。这意味着当我添加粗边框时,行的实际大小比我通过调用从脚本中获得的行大sheet.getRowHeight(i)

\n

结论

\n

我很高兴听到任何想法,包括通过 JavaScript 破解来删除图像下方的空白区域。如果目前不可能,请同时回答并提供文档链接。

\n

Tan*_*ike 4

我相信您的目标如下。

  • 您想要使用 Google Apps 脚本和 Javascript 将范围导出为图像。
  • 为了实现这一点,在这个问题中,您想要计算所选单元格区域的行高。

问题和解决方法:

正如我们在评论中讨论的那样,在现阶段,当试图获取单元格区域的正确行高时,存在以下几个问题。

  • 当单元格使用边框时,行高+边框大小似乎与导出的结果不同。参考号
  • 像素大小可能不会随行高和边框大小的值线性变化。参考号
  • 当我测试包括边框在内的单元格尺寸时,我认为高度和宽度之间的尺寸变化趋势可能不同。参考号
  • 当行高为默认值(从 getRowHeight 为 21)并且单元格中的文本字体大小增大时,getRowHeight 检索到的值不会从 21 发生变化。Ref
  • There is also issue with wrapping text inside a cell which on my experience also causes errors in a pixel size of cell. 参考号
  • 从你的问题来看,当选定的单元格范围很大时,页面数超过2。在这种情况下,所有页面都无法正确合并为图像。

从上述情况来看,我担心获得所选单元格的正确大小可能很困难。所以,我建议将其作为图像处理来处理。Ref我认为当这个过程与图像处理一起运行时,上述问题也许可以避免。

但不幸的是,为了将其作为图像处理进行处理,Google Apps 脚本中没有内置方法。但是,幸运的是,在您的情况下,Javascript 似乎可以在对话框中使用。因此,我创建了一个 Javascript 库来实现图像处理这一过程。参考号

使用该Javascript库时,示例演示如下。

在此输入图像描述

用法:

1. 准备一个电子表格。

请创建一个新的电子表格并在单元格中输入几个值。

2. 示例脚本。

请将以下脚本复制并粘贴到电子表格的脚本编辑器中。

Google Apps 脚本端:Code.gs

function getActiveRange_(ss, borderColor) {
  const space = 5;

  const sheet = ss.getActiveSheet();
  const range = sheet.getActiveRange();
  const obj = { startRow: range.getRow(), startCol: range.getColumn(), endRow: range.getLastRow(), endCol: range.getLastColumn() };
  const temp = sheet.copyTo(ss);
  const r = temp.getDataRange();
  r.copyTo(r, { contentsOnly: true });
  temp.insertRowAfter(obj.endRow).insertRowBefore(obj.startRow).insertColumnAfter(obj.endCol).insertColumnBefore(obj.startCol);
  obj.startRow += 1;
  obj.endRow += 1;
  obj.startCol += 1;
  obj.endCol += 1;
  temp.setRowHeight(obj.startRow - 1, space).setColumnWidth(obj.startCol - 1, space).setRowHeight(obj.endRow + 1, space).setColumnWidth(obj.endCol + 1, space);

  const maxRow = temp.getMaxRows();
  const maxCol = temp.getMaxColumns();
  if (obj.startRow + 1 < maxRow) {
    temp.deleteRows(obj.endRow + 2, maxRow - (obj.endRow + 1));
  }
  if (obj.startCol + 1 < maxCol) {
    temp.deleteColumns(obj.endCol + 2, maxCol - (obj.endCol + 1));
  }
  if (obj.startRow - 1 > 1) {
    temp.deleteRows(1, obj.startRow - 2);
  }
  if (obj.startCol - 1 > 1) {
    temp.deleteColumns(1, obj.startCol - 2);
  }

  const mRow = temp.getMaxRows();
  const mCol = temp.getMaxColumns();
  const clearRanges = [[1, 1, mRow], [1, obj.endCol, mRow], [1, 1, 1, mCol], [obj.endRow, 1, 1, mCol]];
  temp.getRangeList(clearRanges.map(r => temp.getRange(...r).getA1Notation())).clear();

  temp.getRange(1, 1, 1, mCol).setBorder(true, null, null, null, null, null, borderColor, SpreadsheetApp.BorderStyle.SOLID);
  temp.getRange(mRow, 1, 1, mCol).setBorder(null, null, true, null, null, null, borderColor, SpreadsheetApp.BorderStyle.SOLID);

  SpreadsheetApp.flush();
  return temp;
}

function getPDF_(ss, temp) {
  const url = ss.getUrl().replace(/\/edit.*$/, '')
    + '/export?exportFormat=pdf&format=pdf'
    // + '&size=20x20' // If you want to increase the size of one page, please use this. But, when the page size is increased, the process time becomes long. Please be careful about this.
    + '&scale=2'
    + '&top_margin=0.05'
    + '&bottom_margin=0'
    + '&left_margin=0.05'
    + '&right_margin=0'
    + '&sheetnames=false'
    + '&printtitle=false'
    + '&pagenum=UNDEFINED'
    + 'horizontal_alignment=LEFT'
    + '&gridlines=false'
    + "&fmcmd=12"
    + '&fzr=FALSE'
    + '&gid=' + temp.getSheetId();
  const res = UrlFetchApp.fetch(url, { headers: { authorization: "Bearer " + ScriptApp.getOAuthToken() } });
  return "data:application/pdf;base64," + Utilities.base64Encode(res.getContent());
}

// Please run this function.
function main() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const temp = getActiveRange_(ss, "#000000");
  const base64 = getPDF_(ss, temp);
  let htmltext = HtmlService.createTemplateFromFile('index').evaluate().getContent();
  htmltext = htmltext.replace(/IMPORT_PDF_URL/m, base64);
  const html = HtmlService.createTemplate(htmltext).evaluate().setSandboxMode(HtmlService.SandboxMode.NATIVE);
  SpreadsheetApp.getUi().showModalDialog(html, 'sample');
  ss.deleteSheet(temp);
}

function saveFile(data) {
  const blob = Utilities.newBlob(Utilities.base64Decode(data), MimeType.PNG, "sample.png");
  return DriveApp.createFile(blob).getId();
}
Run Code Online (Sandbox Code Playgroud)

HTML 和 JavaScript 方面:index.gs

在这里,我使用了CropImageByBorder_js的 Javascript 库来将其处理为图像处理。

<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/tanaikech/CropImageByBorder_js@latest/cropImageByBorder_js.min.js"></script>
<canvas id="canvas"></canvas>
<script>
  var pdfjsLib = window['pdfjs-dist/build/pdf'];
  pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';
  const base64 = 'IMPORT_PDF_URL'; //Loaading the PDF from URL
  const cvs = document.getElementById("canvas");
  pdfjsLib.getDocument(base64).promise.then(pdf => {
    const {numPages} = pdf;
    if (numPages > 1) {
      throw new Error("Sorry. In the current stage, this sample script can be used for one page of PDF data. So, please change the selected range to smaller.")
    }
    pdf.getPage(1).then(page => {
      const viewport = page.getViewport({scale: 2});
      cvs.height = viewport.height;
      cvs.width = viewport.width;
      const ctx = cvs.getContext('2d');
      const renderContext = { canvasContext: ctx, viewport: viewport };
      page.render(renderContext).promise.then(async function() {
        const obj = { borderColor: "#000000", base64Data: cvs.toDataURL() };
        const base64 = await CropImageByBorder.getInnerImage(obj).catch(err => console.log(err));
        const img = new Image();
        img.src = base64;
        img.onload = function () {
          cvs.width = img.naturalWidth;
          cvs.height = img.naturalHeight;
          ctx.drawImage(img, 0, 0);
        }
        google.script.run.withSuccessHandler(id => console.log(id)).saveFile(base64.split(",").pop());
      });
    });
  });
</script>
Run Code Online (Sandbox Code Playgroud)

3. 测试

当您测试此脚本时,请选择单元格并运行main()。这样,所选单元格将作为图像 (PNG) 导出到根文件夹,如下所示。在这种情况下,您可以看到上面的演示。

在此输入图像描述

4. 流动。

在此示例脚本中,使用以下流程。

  1. 手动选择单元格,然后运行 ​​的脚本main()
  2. 在脚本中,由单行和单列包围的选定单元格被创建为临时表。
  3. 将临时表导出为 Base64 的 PDF 数据。这里,PDF数据被发送到Javascript端。
  4. 使用 PDF.js 将 PDF 数据的第一页转换为图像。
  5. 使用 CropImageByBorder_js 裁剪选定的单元格,并将结果图像返回到 Google Apps 脚本端。
  6. 将图像作为文件保存到 Google 云端硬盘。

局限性:

  • 在此示例脚本中,假设将选定范围放在一个 PDF 页面上。因此,当您选择较大范围时,当 PDF 页数超过 2 时,不幸的是,该脚本无法使用。所以,请小心这一点。
  • 而且,在本例中,Javascript 用于对话框。因此,当您使用此示例脚本时,需要打开电子表格并选择单元格并运行脚本。

笔记:

  • 在您的显示脚本中,为了使用通过 PDF.js 创建的 PDF 数据,需要公开共享电子表格。但是,对于 PDF.js,似乎可以直接使用数据 URL。因此,在此示例脚本中,创建的 PDF 用作数据 URL (base64)。这样,就不需要公开共享电子表格。

  • 更新于 2024 年 1 月 25 日。

参考: