Last active
January 12, 2026 08:34
-
-
Save suzusime/2db0d67fa7082e2b4368ee2cd3559a88 to your computer and use it in GitHub Desktop.
hanzura-calc-deno
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Licensed: CC0 | |
| // https://creativecommons.org/public-domain/cc0/ | |
| // 全体で利用する定数 | |
| const globalConst = { | |
| // 1 ポイントを mm で表した値 | |
| // いわゆるDTPポイント(72分の1インチ)を利用する | |
| point: 25.4 / 72, | |
| // 1 級を mm で表した値 | |
| Q: 0.25, | |
| // 用紙の定義(mm) | |
| paperDef: { | |
| A3: { width: 297, height: 420 }, | |
| A4: { width: 210, height: 297 }, | |
| A5: { width: 148, height: 210 }, | |
| A6: { width: 105, height: 148 }, | |
| B4: { width: 257, height: 364 }, | |
| B5: { width: 182, height: 257 }, | |
| B6: { width: 128, height: 182 }, | |
| } | |
| } as const; | |
| // 用紙種別のキーの型定義 | |
| type PaperType = keyof typeof globalConst.paperDef; | |
| // 設定値の型定義 | |
| interface Config { | |
| paperType: PaperType; | |
| charSize: number; | |
| lineSpacing: number; | |
| targetMarginVertical: number; | |
| targetMarginHorizontal: number; | |
| gridNumRows: number; | |
| gridNumCols: number; | |
| fontAscentRatio: number; | |
| } | |
| // メイン処理 | |
| export function suggestPageLayout(config: Config) { | |
| const paper = globalConst.paperDef[config.paperType]; | |
| const linesPerGrid = calculateLinesPerGrid( | |
| paper.height, | |
| config.targetMarginVertical, | |
| config.charSize, | |
| config.lineSpacing, | |
| config.gridNumRows | |
| ); | |
| const charsPerGrid = calculateCharsPerGrid( | |
| paper.width, | |
| config.targetMarginHorizontal, | |
| config.charSize, | |
| config.lineSpacing, | |
| config.gridNumCols | |
| ); | |
| console.log('--- 前提 ---'); | |
| console.log(`用紙種別: ${config.paperType}`); | |
| console.log(`用紙高さ: ${paper.height} mm`); | |
| console.log(`用紙幅: ${paper.width} mm`); | |
| console.log(`文字サイズ: ${config.charSize.toFixed(2)} mm / ${(config.charSize / globalConst.Q).toFixed(2)} Q / ${(config.charSize / globalConst.point).toFixed(2)} pt`); | |
| console.log(`行間: ${(config.lineSpacing * 100).toFixed(2)} %`); | |
| console.log(`縦のグリッド数: ${config.gridNumRows} 段`); | |
| console.log(`横のグリッド数: ${config.gridNumCols} 列`); | |
| console.log(`上下それぞれの余白目標値: ${config.targetMarginVertical.toFixed(2)} mm`); | |
| console.log(`左右それぞれの余白目標値: ${config.targetMarginHorizontal.toFixed(2)} mm`); | |
| console.log(''); | |
| // 行と列それぞれに対して、+0 / +1 の4パターンを出力する | |
| for(let i = 0; i < 4; i++) { | |
| const lineAddition = i % 2; | |
| const charAddition = Math.floor(i / 2); | |
| printPageLayout(config, i+1, linesPerGrid + lineAddition, charsPerGrid + charAddition); | |
| } | |
| } | |
| // 1グリッドあたりの行数を計算する関数 | |
| // 余白が targetMarginVertical 以上になるような最大の行数を求める | |
| export function calculateLinesPerGrid(paperHeight: number, targetMarginVertical: number, charSize: number, lineSpacing: number, gridNumRows: number){ | |
| // コンテンツエリアの高さ | |
| const contentHeight = paperHeight - 2 * targetMarginVertical; | |
| // 行数を増やしていき、必要なスペースがcontentHeightを超えたら終了する | |
| let result = 0; | |
| for (let i = 0; ; i++) { | |
| // グリッドを綺麗に配置するためには、 lines ≡ -1 (mod gridNumRows) である必要がある | |
| const lines = i * gridNumRows + (gridNumRows - 1); | |
| // 必要な高さを計算する | |
| const requiredHeight = lines * charSize + (lines - 1) * lineSpacing * charSize; | |
| // 必要な高さがコンテンツエリアに収まる場合、結果を更新する | |
| if (requiredHeight <= contentHeight) { | |
| result = i; | |
| } else { | |
| break; | |
| } | |
| } | |
| return result; | |
| } | |
| // 1グリッドあたりの文字数(横幅)を計算する関数 | |
| export function calculateCharsPerGrid(paperWidth: number, targetMarginHorizontal: number, charSize: number, lineSpacing: number, gridNumCols: number) { | |
| // グリッドの横の間隔 | |
| // Affinity の仕様上、縦の間隔と同じ値にする必要がある | |
| const gutterWidth = charSize + 2 * charSize * lineSpacing; | |
| // コンテンツエリアの横幅 | |
| const contentWidth = paperWidth - 2 * targetMarginHorizontal; | |
| // 文字数を増やしていき、必要なスペースがcontentWidthを超えたら終了する | |
| let result = 0; | |
| for (let i=0; ; i++) { | |
| const chars = i * gridNumCols; | |
| // 必要な幅を計算する | |
| const requiredWidth = chars * charSize + (gridNumCols - 1) * gutterWidth; | |
| // 必要な幅がコンテンツエリアに収まる場合、結果を更新する | |
| if (requiredWidth <= contentWidth) { | |
| result = i; | |
| } else { | |
| break; | |
| } | |
| } | |
| return result; | |
| } | |
| export function printPageLayout(config: Config, layoutId: number, linesPerGrid: number, charsPerGrid: number) { | |
| console.log(`--- レイアウト案${layoutId} ---`); | |
| const paper = globalConst.paperDef[config.paperType]; | |
| const linesPerPage = linesPerGrid * config.gridNumRows + (config.gridNumRows - 1); | |
| const contentHeight = linesPerPage * config.charSize + (linesPerPage - 1) * config.lineSpacing * config.charSize; | |
| const marginVertical = (paper.height - contentHeight) / 2; | |
| const gutterWidth = config.charSize + 2 * config.charSize * config.lineSpacing; | |
| const contentWidth = charsPerGrid * config.charSize * config.gridNumCols + (config.gridNumCols - 1) * gutterWidth; | |
| const marginHorizontal = (paper.width - contentWidth) / 2; | |
| const gridHeight = linesPerGrid * config.charSize + (linesPerGrid - 1) * config.lineSpacing * config.charSize; | |
| const gridWidth = charsPerGrid * config.charSize; | |
| const contentHeightPerPaperHeight = contentHeight / paper.height | |
| const contentWidthPerPageWidth = contentWidth / paper.width; | |
| const baselineGridStartY = marginVertical + config.charSize * config.fontAscentRatio; | |
| const baselineGridHeight = config.charSize + config.lineSpacing * config.charSize; | |
| console.log(`1グリッドあたりの行数: ${linesPerGrid} 行`); | |
| console.log(`1ページあたりの行数: ${linesPerPage} 行`); | |
| console.log(`1グリッド内の行あたりの字数: ${charsPerGrid} 字`); | |
| console.log(`1ページあたりの総字数: ${linesPerPage * charsPerGrid * config.gridNumCols} 字`); | |
| console.log(''); | |
| console.log(`上下それぞれの余白: ${marginVertical.toFixed(2)} mm`); | |
| console.log(`左右それぞれの余白: ${marginHorizontal.toFixed(2)} mm`); | |
| console.log('版面比率(縦): ' + (contentHeightPerPaperHeight * 100).toFixed(2) + ' %'); | |
| console.log('版面比率(横): ' + (contentWidthPerPageWidth * 100).toFixed(2) + ' %'); | |
| console.log(''); | |
| console.log(`グリッドの間隔: ${gutterWidth.toFixed(2)} mm / ${(gutterWidth / globalConst.Q).toFixed(2)} H / ${(gutterWidth / globalConst.point).toFixed(2)} pt`); | |
| console.log(`1グリッドの高さ: ${gridHeight.toFixed(2)} mm`); | |
| console.log(`1グリッドの幅: ${gridWidth.toFixed(2)} mm`); | |
| console.log(''); | |
| console.log(`ベースライングリッドの開始位置: ${baselineGridStartY.toFixed(2)} mm / ${(baselineGridStartY / globalConst.Q).toFixed(2)} H / ${(baselineGridStartY / globalConst.point).toFixed(2)} pt`); | |
| console.log(`ベースライングリッドの間隔(高さ): ${baselineGridHeight.toFixed(2)} mm / ${(baselineGridHeight / globalConst.Q).toFixed(2)} H / ${(baselineGridHeight / globalConst.point).toFixed(2)} pt`); | |
| console.log(''); | |
| } | |
| // Learn more at https://docs.deno.com/runtime/manual/examples/module_metadata#concepts | |
| if (import.meta.main) { | |
| // 設定値 | |
| const config: Config = { | |
| // 用紙種別 | |
| paperType: 'A4', | |
| // 文字サイズ | |
| charSize: 12.0 * globalConst.Q, | |
| // 行間(文字サイズに対する割合) | |
| lineSpacing: 0.7, | |
| // 上下それぞれの余白(mm)の目標値 | |
| targetMarginVertical: 10.0, | |
| // 左右それぞれの余白(mm)の目標値 | |
| targetMarginHorizontal: 10.0, | |
| // 縦のグリッド数(段数) | |
| gridNumRows: 4, | |
| // 横のグリッド数(列数) | |
| gridNumCols: 2, | |
| // フォントのグリフ高さのうちベースラインより上の割合 | |
| // (フォントによって異なるが、一般的には0.8~0.9程度) | |
| fontAscentRatio: 0.8, | |
| }; | |
| suggestPageLayout(config); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment