Download src/layout-engine.js from jwcalendar/calendar-layout-engine: direct link, hf CLI and curl.
- Browser
- Download file 6.41 kB
-
https://huggingface.co/spaces/jwcalendar/calendar-layout-engine/resolve/main/src/layout-engine.js
- Command line
-
hf download hf://spaces/jwcalendar/calendar-layout-engine/src/layout-engine.js
-
curl -L -o layout-engine.js https://huggingface.co/spaces/jwcalendar/calendar-layout-engine/resolve/main/src/layout-engine.js
6.41 kB
| export const MONTH_NAMES = ["January","February","March","April","May","June","July","August","September","October","November","December"]; | |
| export const WEEKDAYS = ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"]; | |
| export const PAPER = { A4: { width: 210, height: 297 }, Letter: { width: 215.9, height: 279.4 } }; | |
| function utcDate(year, month, day) { const d=new Date(0);d.setUTCHours(0,0,0,0);d.setUTCFullYear(year,month-1,day);return d; } | |
| function monthGrid(year,month,weekStart,adjacentMonths) { | |
| const first=utcDate(year,month,1).getUTCDay(),offset=(first-weekStart+7)%7,count=utcDate(year,month+1,0).getUTCDate(),rows=Math.ceil((offset+count)/7); | |
| return {year,month,weekStart,firstWeekday:first,daysInMonth:count,rows,leadingEmptyCells:offset,trailingEmptyCells:rows*7-offset-count,cells:Array.from({length:rows*7},(_,i)=>{const d=utcDate(year,month,i-offset+1);return{date:`${d.getUTCFullYear()}-${String(d.getUTCMonth()+1).padStart(2,"0")}-${String(d.getUTCDate()).padStart(2,"0")}`,day:d.getUTCDate(),inMonth:d.getUTCMonth()===month-1,visible:adjacentMonths||d.getUTCMonth()===month-1,weekday:d.getUTCDay()};})}; | |
| } | |
| function isoWeek(dateString) {const [y,m,d]=dateString.split("-").map(Number),date=utcDate(y,m,d),thursday=new Date(date);thursday.setUTCDate(thursday.getUTCDate()+(4-(thursday.getUTCDay()||7)));const isoYear=thursday.getUTCFullYear(),jan4=utcDate(isoYear,1,4);jan4.setUTCDate(jan4.getUTCDate()+(4-(jan4.getUTCDay()||7)));return 1+Math.round((thursday-jan4)/604800000);} | |
| export function calculateLayout(options) { | |
| const { year, month, weekStart, paper, orientation, margin, headerHeight, cellPadding, isoWeekColumn } = options; | |
| const base = PAPER[paper]; | |
| const pageWidth = orientation === "landscape" ? base.height : base.width; | |
| const pageHeight = orientation === "landscape" ? base.width : base.height; | |
| const printableWidth = pageWidth - 2 * margin; | |
| const printableHeight = pageHeight - 2 * margin - headerHeight; | |
| if (printableWidth <= 0 || printableHeight <= 0) throw new RangeError("Margins and header exceed the paper dimensions."); | |
| const grid = monthGrid(year, month, weekStart, options.adjacentMonths); | |
| const columns = 7 + Number(isoWeekColumn); | |
| const cellWidth = printableWidth / columns; | |
| const cellHeight = printableHeight / (grid.rows + 1); | |
| return { ...grid, paper, orientation, pageWidth, pageHeight, margin, headerHeight, printableWidth, printableHeight, rows: grid.rows, columns, cellWidth, cellHeight, cellPadding, safeTextWidth: Math.max(0, cellWidth - 2 * cellPadding), safeTextHeight: Math.max(0, cellHeight - 2 * cellPadding), isoWeekColumn, adjacentMonths: options.adjacentMonths }; | |
| } | |
| export function makeLayoutCss(spec) { | |
| return `@page { size: ${spec.paper} ${spec.orientation}; margin: ${spec.margin}mm; }\n@media print { body { margin: 0; } .calendar-page { width: ${spec.pageWidth - 2*spec.margin}mm; height: ${spec.pageHeight - 2*spec.margin}mm; } }\n.calendar-page { font: 10pt/1.2 system-ui, sans-serif; color: #111; }\n.calendar-page table { width: 100%; height: ${spec.printableHeight}mm; border-collapse: collapse; table-layout: fixed; }\n.calendar-page th, .calendar-page td { border: 0.2mm solid #777; padding: ${spec.cellPadding}mm; vertical-align: top; }\n.calendar-page td { height: ${spec.cellHeight}mm; }`; | |
| } | |
| export function makeHtml(spec, title = `${MONTH_NAMES[spec.month-1]} ${spec.year}`) { | |
| const weekdays = Array.from({length:7},(_,i)=>WEEKDAYS[(spec.weekStart+i)%7]); | |
| const headings = [...(spec.isoWeekColumn ? ["ISO"] : []), ...weekdays]; | |
| const rows = Array.from({length:spec.rows},(_,r)=>{ | |
| const week = spec.cells.slice(r*7,r*7+7); | |
| const days = week.map(cell=>`<td>${cell.inMonth || spec.adjacentMonths ? cell.day : ""}</td>`).join(""); | |
| const thursday=week.find(cell=>cell.weekday===4) ?? week[0]; | |
| return `<tr>${spec.isoWeekColumn ? `<td>W${isoWeek(thursday.date)}</td>` : ""}${days}</tr>`; | |
| }).join("\n"); | |
| return `<!doctype html><html><head><meta charset="utf-8"><title>${title}</title><style>${makeLayoutCss(spec)}</style></head><body><section class="calendar-page"><h1>${title}</h1><table><thead><tr>${headings.map(h=>`<th>${h}</th>`).join("")}</tr></thead><tbody>${rows}</tbody></table></section></body></html>`; | |
| } | |
| export function makeSvg(spec, title = `${MONTH_NAMES[spec.month-1]} ${spec.year}`) { | |
| const mm = value => value * 3.7795275591; | |
| const margin=mm(spec.margin), width=mm(spec.pageWidth),height=mm(spec.pageHeight), gridTop=margin+mm(spec.headerHeight), gridWidth=mm(spec.printableWidth),gridHeight=mm(spec.printableHeight),cellW=gridWidth/spec.columns,cellH=gridHeight/(spec.rows+1); | |
| const parts=[`<svg xmlns="http://www.w3.org/2000/svg" width="${spec.pageWidth}mm" height="${spec.pageHeight}mm" viewBox="0 0 ${width} ${height}">`,`<rect width="100%" height="100%" fill="white"/>`,`<text x="${margin}" y="${margin+mm(8)}" font-family="system-ui,sans-serif" font-size="${mm(6)}" font-weight="700">${title}</text>`]; | |
| const names=[...(spec.isoWeekColumn?["ISO"]:[]),...Array.from({length:7},(_,i)=>WEEKDAYS[(spec.weekStart+i)%7])]; | |
| names.forEach((name,c)=>{const x=margin+c*cellW;parts.push(`<rect x="${x}" y="${gridTop}" width="${cellW}" height="${cellH}" fill="#f2f5f3" stroke="#52615b" stroke-width="0.5"/>`,`<text x="${x+mm(2)}" y="${gridTop+cellH/2}" font-family="system-ui,sans-serif" font-size="${mm(3.4)}">${name.slice(0,3)}</text>`);}); | |
| for(let r=0;r<spec.rows;r++){ | |
| if(spec.isoWeekColumn){const x=margin;const y=gridTop+(r+1)*cellH;const week=spec.cells.slice(r*7,r*7+7);const thursday=week.find(cell=>cell.weekday===4)??week[0];parts.push(`<rect x="${x}" y="${y}" width="${cellW}" height="${cellH}" fill="white" stroke="#52615b" stroke-width="0.5"/><text x="${x+mm(2)}" y="${y+mm(5)}" font-family="system-ui,sans-serif" font-size="${mm(3)}">W${isoWeek(thursday.date)}</text>`);} | |
| spec.cells.slice(r*7,r*7+7).forEach((cell,c)=>{const col=c+Number(spec.isoWeekColumn),x=margin+col*cellW,y=gridTop+(r+1)*cellH;parts.push(`<rect x="${x}" y="${y}" width="${cellW}" height="${cellH}" fill="white" stroke="#52615b" stroke-width="0.5"/>`);if(cell.inMonth||spec.adjacentMonths)parts.push(`<text x="${x+mm(spec.cellPadding)}" y="${y+mm(spec.cellPadding+3)}" font-family="system-ui,sans-serif" font-size="${mm(3.4)}" fill="${cell.inMonth?"#18231f":"#99a39e"}">${cell.day}</text>`);}); | |
| } | |
| parts.push("</svg>");return parts.join("\n"); | |
| } | |