From 4953ddd51d1da5cf025966a927c42f4aaf877e89 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sun, 25 Jan 2026 00:51:23 +0100 Subject: [PATCH 01/22] update: add 3 more border styles --- .../components/BorderPicker/BorderPicker.tsx | 110 +++++++++--- .../WorksheetCanvas/worksheetCanvas.ts | 165 ++++++++++++------ 2 files changed, 196 insertions(+), 79 deletions(-) diff --git a/webapp/IronCalc/src/components/BorderPicker/BorderPicker.tsx b/webapp/IronCalc/src/components/BorderPicker/BorderPicker.tsx index 2ca57b446..3637d809a 100644 --- a/webapp/IronCalc/src/components/BorderPicker/BorderPicker.tsx +++ b/webapp/IronCalc/src/components/BorderPicker/BorderPicker.tsx @@ -1,4 +1,5 @@ import { type BorderOptions, BorderStyle, BorderType } from "@ironcalc/wasm"; +import MenuItem from "@mui/material/MenuItem"; import Popover, { type PopoverOrigin } from "@mui/material/Popover"; import { styled } from "@mui/material/styles"; import { @@ -294,36 +295,60 @@ const BorderPicker = (properties: BorderPickerProps) => { }} > - { setBorderStyle(BorderStyle.Thin); setStylePickerOpen(false); }} - $checked={borderStyle === BorderStyle.Thin} + selected={borderStyle === BorderStyle.Thin} > - Thin - - + { setBorderStyle(BorderStyle.Medium); setStylePickerOpen(false); }} - $checked={borderStyle === BorderStyle.Medium} + selected={borderStyle === BorderStyle.Medium} > - Medium - - + { setBorderStyle(BorderStyle.Thick); setStylePickerOpen(false); }} - $checked={borderStyle === BorderStyle.Thick} + selected={borderStyle === BorderStyle.Thick} > - Thick - + + { + setBorderStyle(BorderStyle.Double); + setStylePickerOpen(false); + }} + selected={borderStyle === BorderStyle.Double} + > + + + { + setBorderStyle(BorderStyle.Dotted); + setStylePickerOpen(false); + }} + selected={borderStyle === BorderStyle.Dotted} + > + + + { + setBorderStyle(BorderStyle.MediumDashed); + setStylePickerOpen(false); + }} + selected={borderStyle === BorderStyle.MediumDashed} + > + + @@ -331,24 +356,23 @@ const BorderPicker = (properties: BorderPickerProps) => { ); }; -type LineWrapperProperties = { $checked: boolean }; -const LineWrapper = styled("div")` +const StyledMenuItem = styled(MenuItem)` display: flex; flex-direction: row; align-items: center; - background-color: ${({ $checked }): string => { - if ($checked) { - return theme.palette.grey["200"]; - } - return "inherit;"; - }}; - &:hover { - border: 1px solid ${theme.palette.grey["200"]}; - } + justify-content: center; + height: 32px; padding: 8px; - cursor: pointer; border-radius: 4px; - border: 1px solid white; + &::before { + content: none; + } + &.Mui-selected { + background-color: ${({ theme }) => theme.palette.action.hover}; + &:hover { + background-color: ${({ theme }) => theme.palette.action.hover}; + } + } `; const SolidLine = styled("div")` @@ -364,6 +388,38 @@ const ThickLine = styled("div")` border-top: 3px solid ${theme.palette.grey["900"]}; `; +const DoubleLine = styled("div")` + width: 68px; + height: 3px; + position: relative; + &::before { + content: ""; + position: absolute; + top: 0; + left: 0; + right: 0; + border-top: 1px solid ${theme.palette.grey["900"]}; + } + &::after { + content: ""; + position: absolute; + bottom: 0; + left: 0; + right: 0; + border-top: 1px solid ${theme.palette.grey["900"]}; + } +`; + +const DottedLine = styled("div")` + width: 68px; + border-top: 1px dotted ${theme.palette.grey["900"]}; +`; + +const MediumDashedLine = styled("div")` + width: 68px; + border-top: 2px dashed ${theme.palette.grey["900"]}; +`; + const Divider = styled("div")` width: 100%; margin: auto; @@ -438,10 +494,6 @@ const BorderPickerDialog = styled("div")` flex-direction: column; `; -const BorderDescription = styled("div")` - width: 70px; -`; - type TypeButtonProperties = { $pressed: boolean; $underlinedColor?: string }; const Button = styled("button")( ({ disabled, $pressed, $underlinedColor }) => { diff --git a/webapp/IronCalc/src/components/WorksheetCanvas/worksheetCanvas.ts b/webapp/IronCalc/src/components/WorksheetCanvas/worksheetCanvas.ts index fe4c6f775..2864473c1 100644 --- a/webapp/IronCalc/src/components/WorksheetCanvas/worksheetCanvas.ts +++ b/webapp/IronCalc/src/components/WorksheetCanvas/worksheetCanvas.ts @@ -709,6 +709,95 @@ export default class WorksheetCanvas { this.cells.push(textProperties); } + /// Helper function to draw a border with different styles + private drawBorder( + context: CanvasRenderingContext2D, + style: string, + color: string, + x1: number, + y1: number, + x2: number, + y2: number, + isVertical: boolean, + ): void { + context.save(); + context.strokeStyle = color; + + switch (style) { + case "thin": + context.lineWidth = 1; + context.beginPath(); + context.moveTo(x1, y1); + context.lineTo(x2, y2); + context.stroke(); + break; + case "medium": + context.lineWidth = 2; + context.beginPath(); + context.moveTo(x1, y1); + context.lineTo(x2, y2); + context.stroke(); + break; + case "thick": + context.lineWidth = 3; + context.beginPath(); + context.moveTo(x1, y1); + context.lineTo(x2, y2); + context.stroke(); + break; + case "double": + // Draw two parallel lines + if (isVertical) { + context.lineWidth = 1; + context.beginPath(); + context.moveTo(x1 - 1, y1); + context.lineTo(x1 - 1, y2); + context.stroke(); + context.beginPath(); + context.moveTo(x1 + 1, y1); + context.lineTo(x1 + 1, y2); + context.stroke(); + } else { + context.lineWidth = 1; + context.beginPath(); + context.moveTo(x1, y1 - 1); + context.lineTo(x2, y1 - 1); + context.stroke(); + context.beginPath(); + context.moveTo(x1, y1 + 1); + context.lineTo(x2, y1 + 1); + context.stroke(); + } + break; + case "dotted": + context.lineWidth = 1; + context.setLineDash([1, 2]); + context.beginPath(); + context.moveTo(x1, y1); + context.lineTo(x2, y2); + context.stroke(); + context.setLineDash([]); + break; + case "mediumdashed": + context.lineWidth = 2; + context.setLineDash([4, 2]); + context.beginPath(); + context.moveTo(x1, y1); + context.lineTo(x2, y2); + context.stroke(); + context.setLineDash([]); + break; + default: + // Fallback to thin for unknown styles + context.lineWidth = 1; + context.beginPath(); + context.moveTo(x1, y1); + context.lineTo(x2, y2); + context.stroke(); + } + context.restore(); + } + /// Renders the cell style: colors, borders, etc. But not the text. private renderCellStyle( row: number, @@ -746,18 +835,10 @@ export default class WorksheetCanvas { // we skip don't draw a left border if it is marked as a "spill cell" if (this.spills.get(`${row}-${column}`) !== 1) { let borderLeftColor = cellGridColor; - let borderLeftWidth = 1; + let borderLeftStyle = "thin"; if (border.left) { borderLeftColor = border.left.color; - switch (border.left.style) { - case "thin": - break; - case "medium": - borderLeftWidth = 2; - break; - case "thick": - borderLeftWidth = 3; - } + borderLeftStyle = border.left.style; } else { const leftStyle = this.model.getCellStyle( selectedSheet, @@ -766,15 +847,7 @@ export default class WorksheetCanvas { ); if (leftStyle.border.right) { borderLeftColor = leftStyle.border.right.color; - switch (leftStyle.border.right.style) { - case "thin": - break; - case "medium": - borderLeftWidth = 2; - break; - case "thick": - borderLeftWidth = 3; - } + borderLeftStyle = leftStyle.border.right.style; } else if (style.fill.fg_color) { borderLeftColor = style.fill.fg_color; } else if (leftStyle.fill.fg_color) { @@ -782,52 +855,44 @@ export default class WorksheetCanvas { } } - context.beginPath(); - context.strokeStyle = borderLeftColor; - context.lineWidth = borderLeftWidth; - context.moveTo(x, y); - context.lineTo(x, y + height); - context.stroke(); + this.drawBorder( + context, + borderLeftStyle, + borderLeftColor, + x, + y, + x, + y + height, + true, + ); } let borderTopColor = cellGridColor; - let borderTopWidth = 1; + let borderTopStyle = "thin"; if (border.top) { borderTopColor = border.top.color; - switch (border.top.style) { - case "thin": - break; - case "medium": - borderTopWidth = 2; - break; - case "thick": - borderTopWidth = 3; - } + borderTopStyle = border.top.style; } else { const topStyle = this.model.getCellStyle(selectedSheet, row - 1, column); if (topStyle.border.bottom) { borderTopColor = topStyle.border.bottom.color; - switch (topStyle.border.bottom.style) { - case "thin": - break; - case "medium": - borderTopWidth = 2; - break; - case "thick": - borderTopWidth = 3; - } + borderTopStyle = topStyle.border.bottom.style; } else if (style.fill.fg_color) { borderTopColor = style.fill.fg_color; } else if (topStyle.fill.fg_color) { borderTopColor = topStyle.fill.fg_color; } } - context.beginPath(); - context.strokeStyle = borderTopColor; - context.lineWidth = borderTopWidth; - context.moveTo(x, y); - context.lineTo(x + width, y); - context.stroke(); + this.drawBorder( + context, + borderTopStyle, + borderTopColor, + x, + y, + x + width, + y, + false, + ); } /// Renders the text in the cell. From 27367b11b7cfa4ad95c660a5f716cfe7c49ff2d5 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sun, 25 Jan 2026 02:06:11 +0100 Subject: [PATCH 02/22] update: use a popper instead of a popover --- .../components/BorderPicker/BorderPicker.tsx | 638 +++++++++--------- .../src/components/Toolbar/Toolbar.tsx | 3 +- 2 files changed, 335 insertions(+), 306 deletions(-) diff --git a/webapp/IronCalc/src/components/BorderPicker/BorderPicker.tsx b/webapp/IronCalc/src/components/BorderPicker/BorderPicker.tsx index 3637d809a..cb1f8d9d6 100644 --- a/webapp/IronCalc/src/components/BorderPicker/BorderPicker.tsx +++ b/webapp/IronCalc/src/components/BorderPicker/BorderPicker.tsx @@ -1,6 +1,7 @@ import { type BorderOptions, BorderStyle, BorderType } from "@ironcalc/wasm"; +import ClickAwayListener from "@mui/material/ClickAwayListener"; import MenuItem from "@mui/material/MenuItem"; -import Popover, { type PopoverOrigin } from "@mui/material/Popover"; +import Popper, { type PopperPlacementType } from "@mui/material/Popper"; import { styled } from "@mui/material/styles"; import { Grid2X2 as BorderAllIcon, @@ -30,8 +31,7 @@ type BorderPickerProps = { onChange: (border: BorderOptions) => void; onClose: () => void; anchorEl: React.RefObject; - anchorOrigin?: PopoverOrigin; - transformOrigin?: PopoverOrigin; + placement: PopperPlacementType; open: boolean; }; @@ -69,290 +69,308 @@ const BorderPicker = (properties: BorderPickerProps) => { const borderColorButton = useRef(null); const borderStyleButton = useRef(null); + + if (!properties.anchorEl.current) { + return null; + } + return ( - -
- - - - - - - - - - - - - - + + + + + + + + + + + + + + + + setColorPickerOpen(true)} + ref={borderColorButton} > - - - - - - - - setColorPickerOpen(true)} - ref={borderColorButton} - > - - -
Border color
- -
+ + Border color + +
- setStylePickerOpen(true)} - ref={borderStyleButton} - > - -
Border style
- -
-
-
- { - setBorderColor(color); - setColorPickerOpen(false); - }} - onClose={() => { - setColorPickerOpen(false); - }} - anchorEl={borderColorButton} - open={colorPickerOpen} - anchorOrigin={{ - vertical: "top", // Keep vertical alignment at the top - horizontal: "right", // Set horizontal alignment to right - }} - transformOrigin={{ - vertical: "top", // Keep vertical alignment at the top - horizontal: "left", // Set horizontal alignment to left - }} - /> - { - setStylePickerOpen(false); - }} - anchorEl={borderStyleButton.current} - anchorOrigin={{ - vertical: "top", - horizontal: "right", - }} - > - - { - setBorderStyle(BorderStyle.Thin); - setStylePickerOpen(false); - }} - selected={borderStyle === BorderStyle.Thin} - > - - - { - setBorderStyle(BorderStyle.Medium); - setStylePickerOpen(false); - }} - selected={borderStyle === BorderStyle.Medium} - > - - - { - setBorderStyle(BorderStyle.Thick); - setStylePickerOpen(false); - }} - selected={borderStyle === BorderStyle.Thick} - > - - - { - setBorderStyle(BorderStyle.Double); - setStylePickerOpen(false); - }} - selected={borderStyle === BorderStyle.Double} - > - - - { - setBorderStyle(BorderStyle.Dotted); - setStylePickerOpen(false); - }} - selected={borderStyle === BorderStyle.Dotted} - > - - - { - setBorderStyle(BorderStyle.MediumDashed); - setStylePickerOpen(false); - }} - selected={borderStyle === BorderStyle.MediumDashed} + setStylePickerOpen(true)} + onMouseLeave={() => setStylePickerOpen(false)} + ref={borderStyleButton} + > + + Border style + + + + + { + setBorderColor(color); + setColorPickerOpen(false); + }} + onClose={() => { + setColorPickerOpen(false); + }} + anchorEl={borderColorButton} + open={colorPickerOpen} + anchorOrigin={{ + vertical: "top", + horizontal: "right", + }} + transformOrigin={{ + vertical: "top", + horizontal: "left", + }} + /> + {borderStyleButton.current && ( + - - - - -
-
+ setStylePickerOpen(true)} + onMouseLeave={() => setStylePickerOpen(false)} + > + { + setBorderStyle(BorderStyle.Thin); + setStylePickerOpen(false); + }} + selected={borderStyle === BorderStyle.Thin} + > + + + { + setBorderStyle(BorderStyle.Medium); + setStylePickerOpen(false); + }} + selected={borderStyle === BorderStyle.Medium} + > + + + { + setBorderStyle(BorderStyle.Thick); + setStylePickerOpen(false); + }} + selected={borderStyle === BorderStyle.Thick} + > + + + { + setBorderStyle(BorderStyle.Double); + setStylePickerOpen(false); + }} + selected={borderStyle === BorderStyle.Double} + > + + + { + setBorderStyle(BorderStyle.Dotted); + setStylePickerOpen(false); + }} + selected={borderStyle === BorderStyle.Dotted} + > + + + { + setBorderStyle(BorderStyle.MediumDashed); + setStylePickerOpen(false); + }} + selected={borderStyle === BorderStyle.MediumDashed} + > + + + + + )} + + + ); }; @@ -446,46 +464,56 @@ const Line = styled("div")` gap: 4px; `; -const ButtonWrapper = styled("div")` +const BaseMenuItem = (props: React.ComponentProps) => ( + +); + +const MenuItemWrapper = styled(BaseMenuItem)` display: flex; - flex-direction: row; - align-items: center; + justify-content: flex-start; border-radius: 4px; - gap: 8px; - &:hover { - background-color: ${theme.palette.grey["200"]}; - border-top-color: ${(): string => theme.palette.grey["200"]}; - } - cursor: pointer; padding: 8px; + height: 32px; + min-height: 32px; + max-height: 32px; + color: ${theme.palette.common.black}; + font-size: 12px; + gap: 8px; svg { - width: 16px; - height: 16px; + max-width: 16px; + min-width: 16px; + max-height: 16px; + min-height: 16px; + color: ${theme.palette.grey[600]}; } `; -const BorderStyleDialog = styled("div")` +const MenuItemText = styled("div")` + flex-grow: 1; +`; + +const PopperContent = styled("div")` + border-radius: 8px; + border: 0px solid ${({ theme }): string => theme.palette.background.default}; + box-shadow: 1px 2px 8px rgba(139, 143, 173, 0.5); background: ${({ theme }): string => theme.palette.background.default}; + font-family: ${({ theme }) => theme.typography.fontFamily}; + font-size: 12px; + overflow: hidden; +`; + +const StylePicker = styled(PopperContent)` padding: 4px; display: flex; flex-direction: column; align-items: center; `; -const StyledPopover = styled(Popover)` - .MuiPopover-paper { - border-radius: 8px; - border: 0px solid ${({ theme }): string => theme.palette.background.default}; - box-shadow: 1px 2px 8px rgba(139, 143, 173, 0.5); - } - .MuiPopover-padding { - padding: 0px; - } - .MuiList-padding { - padding: 0px; +const StyledPopper = styled(Popper)` + z-index: 1300; + &[data-popper-placement] { + pointer-events: auto; } - font-family: ${({ theme }) => theme.typography.fontFamily}; - font-size: 12px; `; const BorderPickerDialog = styled("div")` diff --git a/webapp/IronCalc/src/components/Toolbar/Toolbar.tsx b/webapp/IronCalc/src/components/Toolbar/Toolbar.tsx index 2c708b2ce..491dffc60 100644 --- a/webapp/IronCalc/src/components/Toolbar/Toolbar.tsx +++ b/webapp/IronCalc/src/components/Toolbar/Toolbar.tsx @@ -415,7 +415,7 @@ function Toolbar(properties: ToolbarProperties) { setBorderPickerOpen(true)} ref={borderButton} disabled={!canEdit} @@ -582,6 +582,7 @@ function Toolbar(properties: ToolbarProperties) { transformOrigin={{ vertical: "top", horizontal: "left" }} /> { properties.onBorderChanged(border); }} From 009b06e41f38a9a513d5e7421b76d1ec48162033 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sun, 25 Jan 2026 23:58:05 +0100 Subject: [PATCH 03/22] update: add a timeout in nested menu --- .../components/BorderPicker/BorderPicker.tsx | 34 ++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/webapp/IronCalc/src/components/BorderPicker/BorderPicker.tsx b/webapp/IronCalc/src/components/BorderPicker/BorderPicker.tsx index cb1f8d9d6..50571f66b 100644 --- a/webapp/IronCalc/src/components/BorderPicker/BorderPicker.tsx +++ b/webapp/IronCalc/src/components/BorderPicker/BorderPicker.tsx @@ -69,6 +69,32 @@ const BorderPicker = (properties: BorderPickerProps) => { const borderColorButton = useRef(null); const borderStyleButton = useRef(null); + const stylePickerCloseTimeout = useRef(null); + + const handleStylePickerOpen = () => { + if (stylePickerCloseTimeout.current) { + clearTimeout(stylePickerCloseTimeout.current); + } + setStylePickerOpen(true); + }; + + const handleStylePickerClose = () => { + if (stylePickerCloseTimeout.current) { + clearTimeout(stylePickerCloseTimeout.current); + } + stylePickerCloseTimeout.current = setTimeout(() => { + setStylePickerOpen(false); + }, 150); + }; + + useEffect( + () => () => { + if (stylePickerCloseTimeout.current) { + clearTimeout(stylePickerCloseTimeout.current); + } + }, + [], + ); if (!properties.anchorEl.current) { return null; @@ -260,8 +286,8 @@ const BorderPicker = (properties: BorderPickerProps) => { setStylePickerOpen(true)} - onMouseLeave={() => setStylePickerOpen(false)} + onMouseEnter={handleStylePickerOpen} + onMouseLeave={handleStylePickerClose} ref={borderStyleButton} > @@ -308,8 +334,8 @@ const BorderPicker = (properties: BorderPickerProps) => { ]} > setStylePickerOpen(true)} - onMouseLeave={() => setStylePickerOpen(false)} + onMouseEnter={handleStylePickerOpen} + onMouseLeave={handleStylePickerClose} > { From 6b1e9b734d36c6e218a486c2066658cf1bf5a423 Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 30 Jan 2026 19:07:25 +0100 Subject: [PATCH 04/22] chore: remove duplications in worksheetcanvas --- .../WorksheetCanvas/worksheetCanvas.ts | 85 +++++++++---------- 1 file changed, 41 insertions(+), 44 deletions(-) diff --git a/webapp/IronCalc/src/components/WorksheetCanvas/worksheetCanvas.ts b/webapp/IronCalc/src/components/WorksheetCanvas/worksheetCanvas.ts index 2864473c1..02ebd12db 100644 --- a/webapp/IronCalc/src/components/WorksheetCanvas/worksheetCanvas.ts +++ b/webapp/IronCalc/src/components/WorksheetCanvas/worksheetCanvas.ts @@ -709,6 +709,20 @@ export default class WorksheetCanvas { this.cells.push(textProperties); } + /// Draws a single line from (x1,y1) to (x2,y2) using current stroke style/width. + private drawBorderLine( + context: CanvasRenderingContext2D, + x1: number, + y1: number, + x2: number, + y2: number, + ): void { + context.beginPath(); + context.moveTo(x1, y1); + context.lineTo(x2, y2); + context.stroke(); + } + /// Helper function to draw a border with different styles private drawBorder( context: CanvasRenderingContext2D, @@ -726,74 +740,57 @@ export default class WorksheetCanvas { switch (style) { case "thin": context.lineWidth = 1; - context.beginPath(); - context.moveTo(x1, y1); - context.lineTo(x2, y2); - context.stroke(); + this.drawBorderLine(context, x1, y1, x2, y2); break; case "medium": context.lineWidth = 2; - context.beginPath(); - context.moveTo(x1, y1); - context.lineTo(x2, y2); - context.stroke(); + this.drawBorderLine(context, x1, y1, x2, y2); break; case "thick": context.lineWidth = 3; - context.beginPath(); - context.moveTo(x1, y1); - context.lineTo(x2, y2); - context.stroke(); + this.drawBorderLine(context, x1, y1, x2, y2); break; case "double": // Draw two parallel lines + context.lineWidth = 1; if (isVertical) { - context.lineWidth = 1; - context.beginPath(); - context.moveTo(x1 - 1, y1); - context.lineTo(x1 - 1, y2); - context.stroke(); - context.beginPath(); - context.moveTo(x1 + 1, y1); - context.lineTo(x1 + 1, y2); - context.stroke(); + this.drawBorderLine(context, x1 - 1, y1, x1 - 1, y2); + this.drawBorderLine(context, x1 + 1, y1, x1 + 1, y2); } else { - context.lineWidth = 1; - context.beginPath(); - context.moveTo(x1, y1 - 1); - context.lineTo(x2, y1 - 1); - context.stroke(); - context.beginPath(); - context.moveTo(x1, y1 + 1); - context.lineTo(x2, y1 + 1); - context.stroke(); + this.drawBorderLine(context, x1, y1 - 1, x2, y1 - 1); + this.drawBorderLine(context, x1, y1 + 1, x2, y1 + 1); } break; case "dotted": context.lineWidth = 1; context.setLineDash([1, 2]); - context.beginPath(); - context.moveTo(x1, y1); - context.lineTo(x2, y2); - context.stroke(); + this.drawBorderLine(context, x1, y1, x2, y2); context.setLineDash([]); break; case "mediumdashed": context.lineWidth = 2; context.setLineDash([4, 2]); - context.beginPath(); - context.moveTo(x1, y1); - context.lineTo(x2, y2); - context.stroke(); + this.drawBorderLine(context, x1, y1, x2, y2); context.setLineDash([]); break; - default: - // Fallback to thin for unknown styles + case "slantdashdot": context.lineWidth = 1; - context.beginPath(); - context.moveTo(x1, y1); - context.lineTo(x2, y2); - context.stroke(); + context.setLineDash([4, 2, 1, 2]); + this.drawBorderLine(context, x1, y1, x2, y2); + context.setLineDash([]); + break; + case "mediumdashdot": + context.lineWidth = 2; + context.setLineDash([4, 2, 1, 2]); + this.drawBorderLine(context, x1, y1, x2, y2); + context.setLineDash([]); + break; + case "mediumdashdotdot": + context.lineWidth = 2; + context.setLineDash([4, 2, 1, 2, 1, 2]); + this.drawBorderLine(context, x1, y1, x2, y2); + context.setLineDash([]); + break; } context.restore(); } From ebb0446862abc12ddb518a4914f0c6fd9b0d222c Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 30 Jan 2026 19:09:43 +0100 Subject: [PATCH 05/22] update: add 3 more styles --- .../components/BorderPicker/BorderPicker.tsx | 66 +++++++++++++++++-- 1 file changed, 60 insertions(+), 6 deletions(-) diff --git a/webapp/IronCalc/src/components/BorderPicker/BorderPicker.tsx b/webapp/IronCalc/src/components/BorderPicker/BorderPicker.tsx index 50571f66b..347ffc2ef 100644 --- a/webapp/IronCalc/src/components/BorderPicker/BorderPicker.tsx +++ b/webapp/IronCalc/src/components/BorderPicker/BorderPicker.tsx @@ -391,6 +391,33 @@ const BorderPicker = (properties: BorderPickerProps) => { > + { + setBorderStyle(BorderStyle.SlantDashDot); + setStylePickerOpen(false); + }} + selected={borderStyle === BorderStyle.SlantDashDot} + > + + + { + setBorderStyle(BorderStyle.MediumDashDot); + setStylePickerOpen(false); + }} + selected={borderStyle === BorderStyle.MediumDashDot} + > + + + { + setBorderStyle(BorderStyle.MediumDashDotDot); + setStylePickerOpen(false); + }} + selected={borderStyle === BorderStyle.MediumDashDotDot} + > + + )} @@ -400,6 +427,15 @@ const BorderPicker = (properties: BorderPickerProps) => { ); }; +const borderLineColor = theme.palette.grey["900"]; +const borderLinePreviewWidth = 68; + +const dashDotGradient = (color: string) => + `repeating-linear-gradient(90deg, ${color} 0px 4px, transparent 4px 6px, ${color} 6px 7px, transparent 7px 9px)`; + +const dashDotDotGradient = (color: string) => + `repeating-linear-gradient(90deg, ${color} 0px 4px, transparent 4px 6px, ${color} 6px 7px, transparent 7px 9px, ${color} 9px 10px, transparent 10px 12px)`; + const StyledMenuItem = styled(MenuItem)` display: flex; flex-direction: row; @@ -420,20 +456,20 @@ const StyledMenuItem = styled(MenuItem)` `; const SolidLine = styled("div")` - width: 68px; + width: ${borderLinePreviewWidth}px; border-top: 1px solid ${theme.palette.grey["900"]}; `; const MediumLine = styled("div")` - width: 68px; + width: ${borderLinePreviewWidth}px; border-top: 2px solid ${theme.palette.grey["900"]}; `; const ThickLine = styled("div")` - width: 68px; + width: ${borderLinePreviewWidth}px; border-top: 3px solid ${theme.palette.grey["900"]}; `; const DoubleLine = styled("div")` - width: 68px; + width: ${borderLinePreviewWidth}px; height: 3px; position: relative; &::before { @@ -455,15 +491,33 @@ const DoubleLine = styled("div")` `; const DottedLine = styled("div")` - width: 68px; + width: ${borderLinePreviewWidth}px; border-top: 1px dotted ${theme.palette.grey["900"]}; `; const MediumDashedLine = styled("div")` - width: 68px; + width: ${borderLinePreviewWidth}px; border-top: 2px dashed ${theme.palette.grey["900"]}; `; +const SlantDashDotLine = styled("div")` + width: ${borderLinePreviewWidth}px; + height: 1px; + background: ${dashDotGradient(borderLineColor)}; +`; + +const MediumDashDotLine = styled("div")` + width: ${borderLinePreviewWidth}px; + height: 2px; + background: ${dashDotGradient(borderLineColor)}; +`; + +const MediumDashDotDotLine = styled("div")` + width: ${borderLinePreviewWidth}px; + height: 2px; + background: ${dashDotDotGradient(borderLineColor)}; +`; + const Divider = styled("div")` width: 100%; margin: auto; From 88410df6a3321a5b3d8e66cc378969cace763c49 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 2 Feb 2026 19:39:17 +0100 Subject: [PATCH 06/22] fix: comments --- .../IronCalc/src/components/BorderPicker/BorderPicker.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/webapp/IronCalc/src/components/BorderPicker/BorderPicker.tsx b/webapp/IronCalc/src/components/BorderPicker/BorderPicker.tsx index 347ffc2ef..081c137fc 100644 --- a/webapp/IronCalc/src/components/BorderPicker/BorderPicker.tsx +++ b/webapp/IronCalc/src/components/BorderPicker/BorderPicker.tsx @@ -69,7 +69,9 @@ const BorderPicker = (properties: BorderPickerProps) => { const borderColorButton = useRef(null); const borderStyleButton = useRef(null); - const stylePickerCloseTimeout = useRef(null); + const stylePickerCloseTimeout = useRef | null>( + null, + ); const handleStylePickerOpen = () => { if (stylePickerCloseTimeout.current) { @@ -104,7 +106,7 @@ const BorderPicker = (properties: BorderPickerProps) => { Date: Wed, 4 Feb 2026 13:52:39 +0000 Subject: [PATCH 07/22] fix: treat leading comma as text --- base/src/formatter/format.rs | 5 +++++ base/src/test/test_number_format.rs | 24 ++++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/base/src/formatter/format.rs b/base/src/formatter/format.rs index 41f641ed5..dec8d0bc5 100644 --- a/base/src/formatter/format.rs +++ b/base/src/formatter/format.rs @@ -928,6 +928,11 @@ fn parse_number( } else { 1.0 }; + + if bytes[position] == group_separator { + return Err("Cannot parse number".to_string()); + } + // numbers before the decimal point while position < len { let x = bytes[position]; diff --git a/base/src/test/test_number_format.rs b/base/src/test/test_number_format.rs index e4dcb4a59..3c4ffa71b 100644 --- a/base/src/test/test_number_format.rs +++ b/base/src/test/test_number_format.rs @@ -1,6 +1,8 @@ #![allow(clippy::unwrap_used)] use crate::number_format::format_number; +use crate::test::util::new_empty_model; +use crate::UserModel; #[test] fn test_simple_format() { @@ -17,6 +19,28 @@ fn test_maximum_zeros() { assert_eq!(formatted.text, "1,234.3333333333300000000".to_string()); } +#[test] +fn test_leading_comma_text() { + let model = new_empty_model(); + let mut model = UserModel::from_model(model); + model.set_user_input(0, 1, 1, ",10").unwrap(); // A1 + model.set_user_input(0, 2, 1, ",100").unwrap(); // A2 + model.set_user_input(0, 3, 1, ",1000").unwrap(); // A3 + + assert_eq!( + model.get_formatted_cell_value(0, 1, 1), + Ok(",10".to_string()) + ); + assert_eq!( + model.get_formatted_cell_value(0, 2, 1), + Ok(",100".to_string()) + ); + assert_eq!( + model.get_formatted_cell_value(0, 3, 1), + Ok(",1000".to_string()) + ); +} + #[test] #[ignore = "not yet implemented"] fn test_wrong_locale() { From 8acef664dea68af925fc04a35f728723055f55fd Mon Sep 17 00:00:00 2001 From: Elsa Date: Tue, 3 Feb 2026 23:26:20 +0100 Subject: [PATCH 08/22] fix: add support for booleans and upper bound for df (#734 and #735) --- base/src/functions/statistical/chisq.rs | 38 ++++++++++++++----------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/base/src/functions/statistical/chisq.rs b/base/src/functions/statistical/chisq.rs index 2e0e5c354..b09c7936a 100644 --- a/base/src/functions/statistical/chisq.rs +++ b/base/src/functions/statistical/chisq.rs @@ -13,12 +13,12 @@ impl<'a> Model<'a> { return CalcResult::new_args_number_error(cell); } - let x = match self.get_number_no_bools(&args[0], cell) { + let x = match self.get_number(&args[0], cell) { Ok(f) => f, Err(e) => return e, }; - let df = match self.get_number_no_bools(&args[1], cell) { + let df = match self.get_number(&args[1], cell) { Ok(f) => f.trunc(), Err(e) => return e, }; @@ -35,11 +35,12 @@ impl<'a> Model<'a> { "x must be >= 0 in CHISQ.DIST".to_string(), ); } - if df < 1.0 { + // if degrees of freedom < 1 or > 10^10 → #NUM! + if !(1.0..=10000000000.0).contains(&df) { return CalcResult::new_error( Error::NUM, cell, - "degrees of freedom must be >= 1 in CHISQ.DIST".to_string(), + "degrees of freedom must be in [1, 10^10] in CHISQ.DIST".to_string(), ); } @@ -77,12 +78,12 @@ impl<'a> Model<'a> { return CalcResult::new_args_number_error(cell); } - let x = match self.get_number_no_bools(&args[0], cell) { + let x = match self.get_number(&args[0], cell) { Ok(f) => f, Err(e) => return e, }; - let df_raw = match self.get_number_no_bools(&args[1], cell) { + let df_raw = match self.get_number(&args[1], cell) { Ok(f) => f, Err(e) => return e, }; @@ -96,11 +97,13 @@ impl<'a> Model<'a> { "x must be >= 0 in CHISQ.DIST.RT".to_string(), ); } - if df < 1.0 { + + // if degrees of freedom < 1 or > 10^10 → #NUM! + if !(1.0..=10000000000.0).contains(&df) { return CalcResult::new_error( Error::NUM, cell, - "degrees of freedom must be >= 1 in CHISQ.DIST.RT".to_string(), + "degrees of freedom must be in [1, 10^10] in CHISQ.DIST.RT".to_string(), ); } @@ -136,12 +139,12 @@ impl<'a> Model<'a> { return CalcResult::new_args_number_error(cell); } - let p = match self.get_number_no_bools(&args[0], cell) { + let p = match self.get_number(&args[0], cell) { Ok(f) => f, Err(e) => return e, }; - let df = match self.get_number_no_bools(&args[1], cell) { + let df = match self.get_number(&args[1], cell) { Ok(f) => f.trunc(), Err(e) => return e, }; @@ -154,11 +157,13 @@ impl<'a> Model<'a> { "probability must be in [0,1] in CHISQ.INV".to_string(), ); } - if df < 1.0 { + + // if degrees of freedom < 1 or > 10^10 → #NUM! + if !(1.0..=10000000000.0).contains(&df) { return CalcResult::new_error( Error::NUM, cell, - "degrees of freedom must be >= 1 in CHISQ.INV".to_string(), + "degrees of freedom must be in [1, 10^10] in CHISQ.INV".to_string(), ); } @@ -196,12 +201,12 @@ impl<'a> Model<'a> { return CalcResult::new_args_number_error(cell); } - let p = match self.get_number_no_bools(&args[0], cell) { + let p = match self.get_number(&args[0], cell) { Ok(f) => f, Err(e) => return e, }; - let df_raw = match self.get_number_no_bools(&args[1], cell) { + let df_raw = match self.get_number(&args[1], cell) { Ok(f) => f, Err(e) => return e, }; @@ -216,11 +221,12 @@ impl<'a> Model<'a> { "probability must be in [0,1] in CHISQ.INV.RT".to_string(), ); } - if df < 1.0 { + // if degrees of freedom < 1 or > 10^10 → #NUM! + if !(1.0..=10000000000.0).contains(&df) { return CalcResult::new_error( Error::NUM, cell, - "degrees of freedom must be >= 1 in CHISQ.INV.RT".to_string(), + "degrees of freedom must be in [1, 10^10] in CHISQ.INV.RT".to_string(), ); } From af0a1808557237fbcf8328fc7bd032a15384eee4 Mon Sep 17 00:00:00 2001 From: Elsa Date: Tue, 3 Feb 2026 23:45:57 +0100 Subject: [PATCH 09/22] update: add unit test for bug fix --- base/src/test/statistical/test_fn_chisq.rs | 41 +++++++++++++++++++--- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/base/src/test/statistical/test_fn_chisq.rs b/base/src/test/statistical/test_fn_chisq.rs index fce8ec63d..ea03cdb1f 100644 --- a/base/src/test/statistical/test_fn_chisq.rs +++ b/base/src/test/statistical/test_fn_chisq.rs @@ -22,8 +22,10 @@ fn test_fn_chisq_dist_smoke() { // Domain errors // x < 0 -> #NUM! model._set("A6", "=CHISQ.DIST(-1, 4, TRUE)"); - // deg_freedom < 1 -> #NUM! + // deg_freedom < 1 or > 10^10 -> #NUM! model._set("A7", "=CHISQ.DIST(0.5, 0, TRUE)"); + model._set("A8", "=CHISQ.DIST(10, 10000000000, TRUE)"); + model._set("A9", "=CHISQ.DIST(10, 10000000001, TRUE)"); model.evaluate(); @@ -37,6 +39,8 @@ fn test_fn_chisq_dist_smoke() { assert_eq!(model._get_text("A5"), *"#ERROR!"); assert_eq!(model._get_text("A6"), *"#NUM!"); assert_eq!(model._get_text("A7"), *"#NUM!"); + assert_eq!(model._get_text("A8"), *"0"); + assert_eq!(model._get_text("A9"), *"#NUM!"); } #[test] @@ -54,8 +58,10 @@ fn test_fn_chisq_dist_rt_smoke() { // Domain errors // x < 0 -> #NUM! model._set("A5", "=CHISQ.DIST.RT(-1, 4)"); - // deg_freedom < 1 -> #NUM! + // deg_freedom < 1 or > 10^10 -> #NUM! model._set("A6", "=CHISQ.DIST.RT(0.5, 0)"); + model._set("A7", "=CHISQ.DIST.RT(0, 10000000000)"); + model._set("A8", "=CHISQ.DIST.RT(0, 10000000001)"); model.evaluate(); @@ -69,6 +75,8 @@ fn test_fn_chisq_dist_rt_smoke() { assert_eq!(model._get_text("A4"), *"#ERROR!"); assert_eq!(model._get_text("A5"), *"#NUM!"); assert_eq!(model._get_text("A6"), *"#NUM!"); + assert_eq!(model._get_text("A7"), *"1"); + assert_eq!(model._get_text("A8"), *"#NUM!"); } #[test] @@ -87,8 +95,10 @@ fn test_fn_chisq_inv_smoke() { // probability < 0 or > 1 -> #NUM! model._set("A5", "=CHISQ.INV(-0.1, 4)"); model._set("A6", "=CHISQ.INV(1.1, 4)"); - // deg_freedom < 1 -> #NUM! + // deg_freedom < 1 or > 10^10 -> #NUM! model._set("A7", "=CHISQ.INV(0.5, 0)"); + model._set("A8", "=CHISQ.INV(0, 10000000000)"); + model._set("A9", "=CHISQ.INV(0, 10000000001)"); model.evaluate(); @@ -103,6 +113,8 @@ fn test_fn_chisq_inv_smoke() { assert_eq!(model._get_text("A5"), *"#NUM!"); assert_eq!(model._get_text("A6"), *"#NUM!"); assert_eq!(model._get_text("A7"), *"#NUM!"); + assert_eq!(model._get_text("A8"), *"0"); + assert_eq!(model._get_text("A9"), *"#NUM!"); } #[test] @@ -121,8 +133,10 @@ fn test_fn_chisq_inv_rt_smoke() { // probability < 0 or > 1 -> #NUM! model._set("A5", "=CHISQ.INV.RT(-0.1, 4)"); model._set("A6", "=CHISQ.INV.RT(1.1, 4)"); - // deg_freedom < 1 -> #NUM! + // deg_freedom < 1 or > 10^10 -> #NUM! model._set("A7", "=CHISQ.INV.RT(0.5, 0)"); + model._set("A8", "=CHISQ.INV.RT(1, 10000000000)"); + model._set("A9", "=CHISQ.INV.RT(1, 10000000001)"); model.evaluate(); @@ -137,4 +151,23 @@ fn test_fn_chisq_inv_rt_smoke() { assert_eq!(model._get_text("A5"), *"#NUM!"); assert_eq!(model._get_text("A6"), *"#NUM!"); assert_eq!(model._get_text("A7"), *"#NUM!"); + assert_eq!(model._get_text("A8"), *"0"); + assert_eq!(model._get_text("A9"), *"#NUM!"); +} + +#[test] +fn test_booleans() { + let mut model = new_empty_model(); + + model._set("A1", "=CHISQ.DIST(7, TRUE, TRUE)"); + model._set("A2", "=CHISQ.INV(A1, TRUE)"); + model._set("A3", "=CHISQ.DIST.RT(7, TRUE)"); + model._set("A4", "=CHISQ.INV.RT(A3, TRUE)"); + + model.evaluate(); + + assert_eq!(model._get_text("A1"), "0.991849028"); + assert_eq!(model._get_text("A2"), "7"); + assert_eq!(model._get_text("A3"), "0.008150972"); + assert_eq!(model._get_text("A4"), "7"); } From e22549c3079888c5473dacdae7d21b5c05f0c8a4 Mon Sep 17 00:00:00 2001 From: Elsa Date: Wed, 4 Feb 2026 00:27:50 +0100 Subject: [PATCH 10/22] fix: add max deg of freedom as constant --- base/src/functions/statistical/chisq.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/base/src/functions/statistical/chisq.rs b/base/src/functions/statistical/chisq.rs index b09c7936a..22674a076 100644 --- a/base/src/functions/statistical/chisq.rs +++ b/base/src/functions/statistical/chisq.rs @@ -5,6 +5,7 @@ use crate::expressions::types::CellReferenceIndex; use crate::{ calc_result::CalcResult, expressions::parser::Node, expressions::token::Error, model::Model, }; +const MAX_DEGREES_OF_FREEDOM: f64 = 10_000_000_000.0; impl<'a> Model<'a> { // CHISQ.DIST(x, deg_freedom, cumulative) @@ -36,7 +37,7 @@ impl<'a> Model<'a> { ); } // if degrees of freedom < 1 or > 10^10 → #NUM! - if !(1.0..=10000000000.0).contains(&df) { + if !(1.0..=MAX_DEGREES_OF_FREEDOM).contains(&df) { return CalcResult::new_error( Error::NUM, cell, @@ -99,7 +100,7 @@ impl<'a> Model<'a> { } // if degrees of freedom < 1 or > 10^10 → #NUM! - if !(1.0..=10000000000.0).contains(&df) { + if !(1.0..=MAX_DEGREES_OF_FREEDOM).contains(&df) { return CalcResult::new_error( Error::NUM, cell, @@ -159,7 +160,7 @@ impl<'a> Model<'a> { } // if degrees of freedom < 1 or > 10^10 → #NUM! - if !(1.0..=10000000000.0).contains(&df) { + if !(1.0..=MAX_DEGREES_OF_FREEDOM).contains(&df) { return CalcResult::new_error( Error::NUM, cell, @@ -222,7 +223,7 @@ impl<'a> Model<'a> { ); } // if degrees of freedom < 1 or > 10^10 → #NUM! - if !(1.0..=10000000000.0).contains(&df) { + if !(1.0..=MAX_DEGREES_OF_FREEDOM).contains(&df) { return CalcResult::new_error( Error::NUM, cell, From 38c4787fceab120e9c07a9d21b27b92196bcc6ac Mon Sep 17 00:00:00 2001 From: Elsa Date: Mon, 26 Jan 2026 09:27:25 +0100 Subject: [PATCH 11/22] update: adds locale testing for database functions # Conflicts: # base/src/test/test_database.rs --- base/src/test/test_database.rs | 302 +++++++++++++++++++++++++++++++++ 1 file changed, 302 insertions(+) diff --git a/base/src/test/test_database.rs b/base/src/test/test_database.rs index d20bf3f69..a345ab338 100644 --- a/base/src/test/test_database.rs +++ b/base/src/test/test_database.rs @@ -67,3 +67,305 @@ fn arguments() { assert_eq!(model._get_text("A23"), *"#ERROR!"); assert_eq!(model._get_text("A24"), *"#ERROR!"); } + +#[test] +fn locale_ISO_format() { + // ISO format with YYYY-MM-DD format. Works with any locale. + let mut model = Model::new_empty("model", "en-US", "UTC", "en").unwrap(); + + // Create database + model._set("A1", "ID"); + model._set("B1", "Date"); + model._set("C1", "Amount"); + model._set("A2", "1"); + model._set("B2", "2026-01-15"); + model._set("C2", "1200"); + model._set("A3", "2"); + model._set("B3", "2026-03-15"); + model._set("C3", "900"); + model._set("A4", "3"); + model._set("B4", "2026-06-15"); + model._set("C4", "2100"); + + // Define criteria + model._set("A6", "Date"); + model._set("A7", ">=2026-03-01"); + model._set("B6", "Date"); + model._set("B7", "2026-01-15"); // DGET needs an exact match + + // Test functions + model._set("A9", "=DMIN(A1:C4, C1, A6:A7)"); + model._set("A10", "=DMAX(A1:C4, C1, A6:A7)"); + model._set("A11", "=DAVERAGE(A1:C4, C1, A6:A7)"); + model._set("A12", "=DSUM(A1:C4, C1, A6:A7)"); + model._set("A13", "=DPRODUCT(A1:C4, C1, A6:A7)"); + model._set("A14", "=DGET(A1:C4, C1, B6:B7)"); + model._set("A15", "=DCOUNT(A1:C4, C1, A6:A7)"); + model._set("A16", "=DCOUNTA(A1:C4, C1, A6:A7)"); + model._set("A17", "=DVAR(A1:C4, C1, A6:A7)"); + model._set("A18", "=DVARP(A1:C4, C1, A6:A7)"); + model._set("A19", "=DSTDEV(A1:C4, C1, A6:A7)"); + model._set("A20", "=DSTDEVP(A1:C4, C1, A6:A7)"); + + model.evaluate(); + + assert_eq!(model._get_text("A9"), *"900"); + assert_eq!(model._get_text("A10"), *"2100"); + assert_eq!(model._get_text("A11"), *"1500"); + assert_eq!(model._get_text("A12"), *"3000"); + assert_eq!(model._get_text("A13"), *"1890000"); + assert_eq!(model._get_text("A14"), *"1200"); + assert_eq!(model._get_text("A15"), *"2"); + assert_eq!(model._get_text("A16"), *"2"); + assert_eq!(model._get_text("A17"), *"720000"); + assert_eq!(model._get_text("A18"), *"360000"); + assert_eq!(model._get_text("A19"), *"848.528137423857"); + assert_eq!(model._get_text("A20"), *"600"); +} + +#[test] +fn locale_uk() { + // en-GB locale with DD/MM/YYYY format + let mut model = Model::new_empty("model", "en-GB", "UTC", "en").unwrap(); + + // Create database + model._set("A1", "ID"); + model._set("B1", "Date"); + model._set("C1", "Amount"); + model._set("A2", "1"); + model._set("B2", "15/01/2026"); + model._set("C2", "1200"); + model._set("A3", "2"); + model._set("B3", "15/03/2026"); + model._set("C3", "900"); + model._set("A4", "3"); + model._set("B4", "15/06/2026"); + model._set("C4", "2100"); + + // Define criteria + model._set("A6", "Date"); + model._set("A7", ">=01/03/2026"); + model._set("B6", "Date"); + model._set("B7", "15/01/2026"); // DGET needs an exact match + + // Test functions + model._set("A9", "=DMIN(A1:C4, C1, A6:A7)"); + model._set("A10", "=DMAX(A1:C4, C1, A6:A7)"); + model._set("A11", "=DAVERAGE(A1:C4, C1, A6:A7)"); + model._set("A12", "=DSUM(A1:C4, C1, A6:A7)"); + model._set("A13", "=DPRODUCT(A1:C4, C1, A6:A7)"); + model._set("A14", "=DGET(A1:C4, C1, B6:B7)"); + model._set("A15", "=DCOUNT(A1:C4, C1, A6:A7)"); + model._set("A16", "=DCOUNTA(A1:C4, C1, A6:A7)"); + model._set("A17", "=DVAR(A1:C4, C1, A6:A7)"); + model._set("A18", "=DVARP(A1:C4, C1, A6:A7)"); + model._set("A19", "=DSTDEV(A1:C4, C1, A6:A7)"); + model._set("A20", "=DSTDEVP(A1:C4, C1, A6:A7)"); + + model.evaluate(); + + assert_eq!(model._get_text("A9"), *"900"); + assert_eq!(model._get_text("A10"), *"2100"); + assert_eq!(model._get_text("A11"), *"1500"); + assert_eq!(model._get_text("A12"), *"3000"); + assert_eq!(model._get_text("A13"), *"1890000"); + assert_eq!(model._get_text("A14"), *"1200"); + assert_eq!(model._get_text("A15"), *"2"); + assert_eq!(model._get_text("A16"), *"2"); + assert_eq!(model._get_text("A17"), *"720000"); + assert_eq!(model._get_text("A18"), *"360000"); + assert_eq!(model._get_text("A19"), *"848.528137423857"); + assert_eq!(model._get_text("A20"), *"600"); +} + +#[test] +fn locale_us() { + // en-US locale with MM/DD/YY format + let mut model = Model::new_empty("model", "en-US", "UTC", "en").unwrap(); + + // Create database + model._set("A1", "ID"); + model._set("B1", "Date"); + model._set("C1", "Amount"); + model._set("A2", "1"); + model._set("B2", "1/15/26"); + model._set("C2", "1200"); + model._set("A3", "2"); + model._set("B3", "3/15/26"); + model._set("C3", "900"); + model._set("A4", "3"); + model._set("B4", "6/15/26"); + model._set("C4", "2100"); + + // Define criteria + model._set("A6", "Date"); + model._set("A7", ">=3/1/26"); + model._set("B6", "Date"); + model._set("B7", "1/15/26"); // DGET needs an exact match + + // Test functions + model._set("A9", "=DMIN(A1:C4, C1, A6:A7)"); + model._set("A10", "=DMAX(A1:C4, C1, A6:A7)"); + model._set("A11", "=DAVERAGE(A1:C4, C1, A6:A7)"); + model._set("A12", "=DSUM(A1:C4, C1, A6:A7)"); + model._set("A13", "=DPRODUCT(A1:C4, C1, A6:A7)"); + model._set("A14", "=DGET(A1:C4, C1, B6:B7)"); + model._set("A15", "=DCOUNT(A1:C4, C1, A6:A7)"); + model._set("A16", "=DCOUNTA(A1:C4, C1, A6:A7)"); + model._set("A17", "=DVAR(A1:C4, C1, A6:A7)"); + model._set("A18", "=DVARP(A1:C4, C1, A6:A7)"); + model._set("A19", "=DSTDEV(A1:C4, C1, A6:A7)"); + model._set("A20", "=DSTDEVP(A1:C4, C1, A6:A7)"); + + model.evaluate(); + + assert_eq!(model._get_text("A9"), *"900"); + assert_eq!(model._get_text("A10"), *"2100"); + assert_eq!(model._get_text("A11"), *"1500"); + assert_eq!(model._get_text("A12"), *"3000"); + assert_eq!(model._get_text("A13"), *"1890000"); + assert_eq!(model._get_text("A14"), *"1200"); + assert_eq!(model._get_text("A15"), *"2"); + assert_eq!(model._get_text("A16"), *"2"); + assert_eq!(model._get_text("A17"), *"720000"); + assert_eq!(model._get_text("A18"), *"360000"); + assert_eq!(model._get_text("A19"), *"848.528137423857"); + assert_eq!(model._get_text("A20"), *"600"); +} + +#[test] +fn locale_de() { + // de-DE locale with D.M.YYYY format + let mut model = Model::new_empty("model", "de-DE", "UTC", "en").unwrap(); + + // Create database + model._set("A1", "ID"); + model._set("B1", "Date"); + model._set("C1", "Amount"); + model._set("A2", "1"); + model._set("B2", "15.1.2026"); + model._set("C2", "1200"); + model._set("A3", "2"); + model._set("B3", "15.3.2026"); + model._set("C3", "900"); + model._set("A4", "3"); + model._set("B4", "15.6.2026"); + model._set("C4", "2100"); + + // Define criteria + model._set("A6", "Date"); + model._set("A7", ">=1.3.2026"); + model._set("B6", "Date"); + model._set("B7", "15.1.2026"); // DGET needs an exact match + + // Test functions + model._set("A9", "=DMIN(A1:C4, C1, A6:A7)"); + model._set("A10", "=DMAX(A1:C4, C1, A6:A7)"); + model._set("A11", "=DAVERAGE(A1:C4, C1, A6:A7)"); + model._set("A12", "=DSUM(A1:C4, C1, A6:A7)"); + model._set("A13", "=DPRODUCT(A1:C4, C1, A6:A7)"); + model._set("A14", "=DGET(A1:C4, C1, B6:B7)"); + model._set("A15", "=DCOUNT(A1:C4, C1, A6:A7)"); + model._set("A16", "=DCOUNTA(A1:C4, C1, A6:A7)"); + model._set("A17", "=DVAR(A1:C4, C1, A6:A7)"); + model._set("A18", "=DVARP(A1:C4, C1, A6:A7)"); + model._set("A19", "=DSTDEV(A1:C4, C1, A6:A7)"); + model._set("A20", "=DSTDEVP(A1:C4, C1, A6:A7)"); + + model.evaluate(); + + assert_eq!(model._get_text("A9"), *"900"); + assert_eq!(model._get_text("A10"), *"2100"); + assert_eq!(model._get_text("A11"), *"1500"); + assert_eq!(model._get_text("A12"), *"3000"); + assert_eq!(model._get_text("A13"), *"1890000"); + assert_eq!(model._get_text("A14"), *"1200"); + assert_eq!(model._get_text("A15"), *"2"); + assert_eq!(model._get_text("A16"), *"2"); + assert_eq!(model._get_text("A17"), *"720000"); + assert_eq!(model._get_text("A18"), *"360000"); + assert_eq!(model._get_text("A19"), *"848.528137423857"); + assert_eq!(model._get_text("A20"), *"600"); +} + +#[test] +fn locale_wrong_format() { + // en-US locale with incorrect D.M.YYYY format + let mut model = Model::new_empty("model", "en-US", "UTC", "en").unwrap(); + + // Create database + model._set("A1", "ID"); + model._set("B1", "Date"); + model._set("C1", "Amount"); + model._set("A2", "1"); + model._set("B2", "15.1.2026"); + model._set("C2", "1200"); + model._set("A3", "2"); + model._set("B3", "15.3.2026"); + model._set("C3", "900"); + model._set("A4", "3"); + model._set("B4", "15.6.2026"); + model._set("C4", "2100"); + + // Define criteria + model._set("A6", "Date"); + model._set("A7", ">=1.3.2026"); + model._set("B6", "Date"); + model._set("B7", "15.1.2026"); // DGET needs an exact match + + // Test functions - results should be the same as using empty criteria + model._set("A9", "=DMIN(A1:C4, C1, A6:A7)"); + model._set("A10", "=DMAX(A1:C4, C1, A6:A7)"); + model._set("A11", "=DAVERAGE(A1:C4, C1, A6:A7)"); + model._set("A12", "=DSUM(A1:C4, C1, A6:A7)"); + model._set("A13", "=DPRODUCT(A1:C4, C1, A6:A7)"); + model._set("A14", "=DGET(A1:C4, C1, B6:B7)"); + model._set("A15", "=DCOUNT(A1:C4, C1, A6:A7)"); + model._set("A16", "=DCOUNTA(A1:C4, C1, A6:A7)"); + model._set("A17", "=DVAR(A1:C4, C1, A6:A7)"); + model._set("A18", "=DVARP(A1:C4, C1, A6:A7)"); + model._set("A19", "=DSTDEV(A1:C4, C1, A6:A7)"); + model._set("A20", "=DSTDEVP(A1:C4, C1, A6:A7)"); + + // Test functions with empty criteria - range C6:C7 is empty + model._set("B9", "=DMIN(A1:C4, C1, C6:C7)"); + model._set("B10", "=DMAX(A1:C4, C1, C6:C7)"); + model._set("B11", "=DAVERAGE(A1:C4, C1, C6:C7)"); + model._set("B12", "=DSUM(A1:C4, C1, C6:C7)"); + model._set("B13", "=DPRODUCT(A1:C4, C1, C6:C7)"); + model._set("B14", "=DGET(A1:C4, C1, C6:C7)"); + model._set("B15", "=DCOUNT(A1:C4, C1, C6:C7)"); + model._set("B16", "=DCOUNTA(A1:C4, C1, C6:C7)"); + model._set("B17", "=DVAR(A1:C4, C1, C6:C7)"); + model._set("B18", "=DVARP(A1:C4, C1, C6:C7)"); + model._set("B19", "=DSTDEV(A1:C4, C1, C6:C7)"); + model._set("B20", "=DSTDEVP(A1:C4, C1, C6:C7)"); + + model.evaluate(); + + assert_eq!(model._get_text("A9"), *"900"); + assert_eq!(model._get_text("A10"), *"2100"); + assert_eq!(model._get_text("A11"), *"1400"); + assert_eq!(model._get_text("A12"), *"4200"); + assert_eq!(model._get_text("A13"), *"2268000000"); + assert_eq!(model._get_text("A14"), *"1200"); + assert_eq!(model._get_text("A15"), *"3"); + assert_eq!(model._get_text("A16"), *"3"); + assert_eq!(model._get_text("A17"), *"390000"); + assert_eq!(model._get_text("A18"), *"260000"); + assert_eq!(model._get_text("A19"), *"624.49979983984"); + assert_eq!(model._get_text("A20"), *"509.901951359278"); + + assert_eq!(model._get_text("B9"), *"900"); + assert_eq!(model._get_text("B10"), *"2100"); + assert_eq!(model._get_text("B11"), *"1400"); + assert_eq!(model._get_text("B12"), *"4200"); + assert_eq!(model._get_text("B13"), *"2268000000"); + assert_eq!(model._get_text("B14"), *"1200"); + assert_eq!(model._get_text("B15"), *"3"); + assert_eq!(model._get_text("B16"), *"3"); + assert_eq!(model._get_text("B17"), *"390000"); + assert_eq!(model._get_text("B18"), *"260000"); + assert_eq!(model._get_text("B19"), *"624.49979983984"); + assert_eq!(model._get_text("B20"), *"509.901951359278"); +} From d6ea5e62804067b9eebd7c83c94485635e8fbe2d Mon Sep 17 00:00:00 2001 From: Elsa Date: Mon, 26 Jan 2026 17:12:17 +0100 Subject: [PATCH 12/22] fix: changes to locale name, use correct formula delimitter for de --- base/src/test/test_database.rs | 61 +++++++++++++++++----------------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/base/src/test/test_database.rs b/base/src/test/test_database.rs index a345ab338..4e8bc93f6 100644 --- a/base/src/test/test_database.rs +++ b/base/src/test/test_database.rs @@ -1,6 +1,7 @@ #![allow(clippy::unwrap_used)] use crate::test::util::new_empty_model; +use crate::Model; #[test] fn arguments() { @@ -69,9 +70,9 @@ fn arguments() { } #[test] -fn locale_ISO_format() { +fn locale_iso_format() { // ISO format with YYYY-MM-DD format. Works with any locale. - let mut model = Model::new_empty("model", "en-US", "UTC", "en").unwrap(); + let mut model = Model::new_empty("model", "en", "UTC", "en").unwrap(); // Create database model._set("A1", "ID"); @@ -119,7 +120,7 @@ fn locale_ISO_format() { assert_eq!(model._get_text("A16"), *"2"); assert_eq!(model._get_text("A17"), *"720000"); assert_eq!(model._get_text("A18"), *"360000"); - assert_eq!(model._get_text("A19"), *"848.528137423857"); + assert_eq!(model._get_text("A19"), *"848.528137424"); assert_eq!(model._get_text("A20"), *"600"); } @@ -174,14 +175,14 @@ fn locale_uk() { assert_eq!(model._get_text("A16"), *"2"); assert_eq!(model._get_text("A17"), *"720000"); assert_eq!(model._get_text("A18"), *"360000"); - assert_eq!(model._get_text("A19"), *"848.528137423857"); + assert_eq!(model._get_text("A19"), *"848.528137424"); assert_eq!(model._get_text("A20"), *"600"); } #[test] fn locale_us() { // en-US locale with MM/DD/YY format - let mut model = Model::new_empty("model", "en-US", "UTC", "en").unwrap(); + let mut model = Model::new_empty("model", "en", "UTC", "en").unwrap(); // Create database model._set("A1", "ID"); @@ -229,14 +230,14 @@ fn locale_us() { assert_eq!(model._get_text("A16"), *"2"); assert_eq!(model._get_text("A17"), *"720000"); assert_eq!(model._get_text("A18"), *"360000"); - assert_eq!(model._get_text("A19"), *"848.528137423857"); + assert_eq!(model._get_text("A19"), *"848.528137424"); assert_eq!(model._get_text("A20"), *"600"); } #[test] fn locale_de() { // de-DE locale with D.M.YYYY format - let mut model = Model::new_empty("model", "de-DE", "UTC", "en").unwrap(); + let mut model = Model::new_empty("model", "de", "UTC", "en").unwrap(); // Create database model._set("A1", "ID"); @@ -259,18 +260,18 @@ fn locale_de() { model._set("B7", "15.1.2026"); // DGET needs an exact match // Test functions - model._set("A9", "=DMIN(A1:C4, C1, A6:A7)"); - model._set("A10", "=DMAX(A1:C4, C1, A6:A7)"); - model._set("A11", "=DAVERAGE(A1:C4, C1, A6:A7)"); - model._set("A12", "=DSUM(A1:C4, C1, A6:A7)"); - model._set("A13", "=DPRODUCT(A1:C4, C1, A6:A7)"); - model._set("A14", "=DGET(A1:C4, C1, B6:B7)"); - model._set("A15", "=DCOUNT(A1:C4, C1, A6:A7)"); - model._set("A16", "=DCOUNTA(A1:C4, C1, A6:A7)"); - model._set("A17", "=DVAR(A1:C4, C1, A6:A7)"); - model._set("A18", "=DVARP(A1:C4, C1, A6:A7)"); - model._set("A19", "=DSTDEV(A1:C4, C1, A6:A7)"); - model._set("A20", "=DSTDEVP(A1:C4, C1, A6:A7)"); + model._set("A9", "=DMIN(A1:C4; C1; A6:A7)"); + model._set("A10", "=DMAX(A1:C4; C1; A6:A7)"); + model._set("A11", "=DAVERAGE(A1:C4; C1; A6:A7)"); + model._set("A12", "=DSUM(A1:C4; C1; A6:A7)"); + model._set("A13", "=DPRODUCT(A1:C4; C1; A6:A7)"); + model._set("A14", "=DGET(A1:C4; C1; B6:B7)"); + model._set("A15", "=DCOUNT(A1:C4; C1; A6:A7)"); + model._set("A16", "=DCOUNTA(A1:C4; C1; A6:A7)"); + model._set("A17", "=DVAR(A1:C4; C1; A6:A7)"); + model._set("A18", "=DVARP(A1:C4; C1; A6:A7)"); + model._set("A19", "=DSTDEV(A1:C4; C1; A6:A7)"); + model._set("A20", "=DSTDEVP(A1:C4; C1; A6:A7)"); model.evaluate(); @@ -284,34 +285,34 @@ fn locale_de() { assert_eq!(model._get_text("A16"), *"2"); assert_eq!(model._get_text("A17"), *"720000"); assert_eq!(model._get_text("A18"), *"360000"); - assert_eq!(model._get_text("A19"), *"848.528137423857"); + assert_eq!(model._get_text("A19"), *"848,528137424"); assert_eq!(model._get_text("A20"), *"600"); } #[test] fn locale_wrong_format() { // en-US locale with incorrect D.M.YYYY format - let mut model = Model::new_empty("model", "en-US", "UTC", "en").unwrap(); + let mut model = Model::new_empty("model", "en", "UTC", "en").unwrap(); // Create database model._set("A1", "ID"); model._set("B1", "Date"); model._set("C1", "Amount"); model._set("A2", "1"); - model._set("B2", "15.1.2026"); + model._set("B2", "10.1.2026"); model._set("C2", "1200"); model._set("A3", "2"); - model._set("B3", "15.3.2026"); + model._set("B3", "10.3.2026"); model._set("C3", "900"); model._set("A4", "3"); - model._set("B4", "15.6.2026"); + model._set("B4", "10.6.2026"); model._set("C4", "2100"); // Define criteria model._set("A6", "Date"); model._set("A7", ">=1.3.2026"); model._set("B6", "Date"); - model._set("B7", "15.1.2026"); // DGET needs an exact match + model._set("B7", "10.1.2026"); // DGET needs an exact match // Test functions - results should be the same as using empty criteria model._set("A9", "=DMIN(A1:C4, C1, A6:A7)"); @@ -333,7 +334,7 @@ fn locale_wrong_format() { model._set("B11", "=DAVERAGE(A1:C4, C1, C6:C7)"); model._set("B12", "=DSUM(A1:C4, C1, C6:C7)"); model._set("B13", "=DPRODUCT(A1:C4, C1, C6:C7)"); - model._set("B14", "=DGET(A1:C4, C1, C6:C7)"); + model._set("B14", "=DGET(A1:C4, C1, B6:B7)"); model._set("B15", "=DCOUNT(A1:C4, C1, C6:C7)"); model._set("B16", "=DCOUNTA(A1:C4, C1, C6:C7)"); model._set("B17", "=DVAR(A1:C4, C1, C6:C7)"); @@ -353,8 +354,8 @@ fn locale_wrong_format() { assert_eq!(model._get_text("A16"), *"3"); assert_eq!(model._get_text("A17"), *"390000"); assert_eq!(model._get_text("A18"), *"260000"); - assert_eq!(model._get_text("A19"), *"624.49979983984"); - assert_eq!(model._get_text("A20"), *"509.901951359278"); + assert_eq!(model._get_text("A19"), *"624.49979984"); + assert_eq!(model._get_text("A20"), *"509.901951359"); assert_eq!(model._get_text("B9"), *"900"); assert_eq!(model._get_text("B10"), *"2100"); @@ -366,6 +367,6 @@ fn locale_wrong_format() { assert_eq!(model._get_text("B16"), *"3"); assert_eq!(model._get_text("B17"), *"390000"); assert_eq!(model._get_text("B18"), *"260000"); - assert_eq!(model._get_text("B19"), *"624.49979983984"); - assert_eq!(model._get_text("B20"), *"509.901951359278"); + assert_eq!(model._get_text("B19"), *"624.49979984"); + assert_eq!(model._get_text("B20"), *"509.901951359"); } From d4015cdf7baa1e667fe4b911ba210d6f9739f649 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Hatcher?= Date: Sat, 7 Feb 2026 02:38:39 +0100 Subject: [PATCH 13/22] FIX: Cut and paste on partially selected range --- base/src/test/user_model/mod.rs | 1 + base/src/test/user_model/test_cut_n_paste.rs | 99 ++++++++++++++++++++ base/src/user_model/common.rs | 11 ++- 3 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 base/src/test/user_model/test_cut_n_paste.rs diff --git a/base/src/test/user_model/mod.rs b/base/src/test/user_model/mod.rs index 89d346419..122daa25c 100644 --- a/base/src/test/user_model/mod.rs +++ b/base/src/test/user_model/mod.rs @@ -5,6 +5,7 @@ mod test_batch_row_column_diff; mod test_border; mod test_clear_cells; mod test_column_style; +mod test_cut_n_paste; mod test_defined_names; mod test_delete_row_column_formatting; mod test_diff_queue; diff --git a/base/src/test/user_model/test_cut_n_paste.rs b/base/src/test/user_model/test_cut_n_paste.rs new file mode 100644 index 000000000..7e3907e93 --- /dev/null +++ b/base/src/test/user_model/test_cut_n_paste.rs @@ -0,0 +1,99 @@ +#![allow(clippy::unwrap_used)] + +use crate::test::util::new_empty_model; +use crate::UserModel; + +#[test] +fn cun_n_paste_same_area() { + let model = new_empty_model(); + let mut model = UserModel::from_model(model); + // B3:D5 with data + model.set_user_input(0, 3, 2, "A").unwrap(); + model.set_user_input(0, 3, 3, "B").unwrap(); + model.set_user_input(0, 3, 4, "C").unwrap(); + model.set_user_input(0, 4, 2, "D").unwrap(); + model.set_user_input(0, 4, 3, "E").unwrap(); + model.set_user_input(0, 4, 4, "F").unwrap(); + model.set_user_input(0, 5, 2, "G").unwrap(); + model.set_user_input(0, 5, 3, "H").unwrap(); + model.set_user_input(0, 5, 4, "I").unwrap(); + + // Cut it and paste it in C4 + model.set_selected_cell(3, 2).unwrap(); + model.set_selected_range(3, 2, 5, 4).unwrap(); + let cp = model.copy_to_clipboard().unwrap(); + + // C4 + model.set_selected_cell(4, 3).unwrap(); + + let source_range = (3, 2, 5, 4); + model + .paste_from_clipboard(0, source_range, &cp.data, true) + .unwrap(); + + // Check data is in C4:E6 + assert_eq!(model.get_formatted_cell_value(0, 4, 3).unwrap(), "A"); + assert_eq!(model.get_formatted_cell_value(0, 4, 4).unwrap(), "B"); + assert_eq!(model.get_formatted_cell_value(0, 4, 5).unwrap(), "C"); + assert_eq!(model.get_formatted_cell_value(0, 5, 3).unwrap(), "D"); + assert_eq!(model.get_formatted_cell_value(0, 5, 4).unwrap(), "E"); + assert_eq!(model.get_formatted_cell_value(0, 5, 5).unwrap(), "F"); + assert_eq!(model.get_formatted_cell_value(0, 6, 3).unwrap(), "G"); + assert_eq!(model.get_formatted_cell_value(0, 6, 4).unwrap(), "H"); + assert_eq!(model.get_formatted_cell_value(0, 6, 5).unwrap(), "I"); +} + +#[test] +fn cun_n_paste_different_sheet() { + let model = new_empty_model(); + let mut model = UserModel::from_model(model); + // B3:D5 with data + model.set_user_input(0, 3, 2, "A").unwrap(); + model.set_user_input(0, 3, 3, "B").unwrap(); + model.set_user_input(0, 3, 4, "C").unwrap(); + model.set_user_input(0, 4, 2, "D").unwrap(); + model.set_user_input(0, 4, 3, "E").unwrap(); + model.set_user_input(0, 4, 4, "F").unwrap(); + model.set_user_input(0, 5, 2, "G").unwrap(); + model.set_user_input(0, 5, 3, "H").unwrap(); + model.set_user_input(0, 5, 4, "I").unwrap(); + + // Cut it and paste it in C4 + model.set_selected_cell(3, 2).unwrap(); + model.set_selected_range(3, 2, 5, 4).unwrap(); + let cp = model.copy_to_clipboard().unwrap(); + + // New sheet and select it + model.new_sheet().unwrap(); + model.set_selected_sheet(1).unwrap(); + + // C4 + model.set_selected_cell(4, 3).unwrap(); + + let source_range = (3, 2, 5, 4); + model + .paste_from_clipboard(0, source_range, &cp.data, true) + .unwrap(); + + // Check data is in Sheet2!C4:E6 + assert_eq!(model.get_formatted_cell_value(1, 4, 3).unwrap(), "A"); + assert_eq!(model.get_formatted_cell_value(1, 4, 4).unwrap(), "B"); + assert_eq!(model.get_formatted_cell_value(1, 4, 5).unwrap(), "C"); + assert_eq!(model.get_formatted_cell_value(1, 5, 3).unwrap(), "D"); + assert_eq!(model.get_formatted_cell_value(1, 5, 4).unwrap(), "E"); + assert_eq!(model.get_formatted_cell_value(1, 5, 5).unwrap(), "F"); + assert_eq!(model.get_formatted_cell_value(1, 6, 3).unwrap(), "G"); + assert_eq!(model.get_formatted_cell_value(1, 6, 4).unwrap(), "H"); + assert_eq!(model.get_formatted_cell_value(1, 6, 5).unwrap(), "I"); + + // Check original range is empty Sheet1!B3:D5 + assert_eq!(model.get_formatted_cell_value(0, 3, 2).unwrap(), ""); + assert_eq!(model.get_formatted_cell_value(0, 3, 3).unwrap(), ""); + assert_eq!(model.get_formatted_cell_value(0, 3, 4).unwrap(), ""); + assert_eq!(model.get_formatted_cell_value(0, 4, 2).unwrap(), ""); + assert_eq!(model.get_formatted_cell_value(0, 4, 3).unwrap(), ""); + assert_eq!(model.get_formatted_cell_value(0, 4, 4).unwrap(), ""); + assert_eq!(model.get_formatted_cell_value(0, 5, 2).unwrap(), ""); + assert_eq!(model.get_formatted_cell_value(0, 5, 3).unwrap(), ""); + assert_eq!(model.get_formatted_cell_value(0, 5, 4).unwrap(), ""); +} diff --git a/base/src/user_model/common.rs b/base/src/user_model/common.rs index 1c635d5fb..190a9a2a8 100644 --- a/base/src/user_model/common.rs +++ b/base/src/user_model/common.rs @@ -1,6 +1,10 @@ #![deny(missing_docs)] -use std::{collections::HashMap, fmt::Debug, io::Cursor}; +use std::{ + collections::{HashMap, HashSet}, + fmt::Debug, + io::Cursor, +}; use csv::{ReaderBuilder, WriterBuilder}; use serde::{Deserialize, Serialize}; @@ -1830,6 +1834,7 @@ impl<'a> UserModel<'a> { width: source_last_column - source_first_column + 1, height: source_last_row - source_first_row + 1, }; + let mut seen_cells = HashSet::new(); for (source_row, data_row) in clipboard { let delta_row = source_row - source_first_row; let target_row = selected_row + delta_row; @@ -1893,11 +1898,15 @@ impl<'a> UserModel<'a> { old_value: Box::new(old_style), new_value: Box::new(value.style.clone()), }); + seen_cells.insert((target_row, target_column)); } } if is_cut { for row in source_first_row..=source_last_row { for column in source_first_column..=source_last_column { + if (source_sheet == sheet) && seen_cells.contains(&(row, column)) { + continue; + } let old_value = self .model .workbook From 21631e0ca95fdf937562441d1efaacb3dd0a079b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Hatcher?= Date: Sat, 7 Feb 2026 11:02:45 +0100 Subject: [PATCH 14/22] FIX: Adds missing test --- base/src/test/user_model/mod.rs | 1 + base/src/test/user_model/test_fn_formulatext.rs | 13 ++++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/base/src/test/user_model/mod.rs b/base/src/test/user_model/mod.rs index 122daa25c..386c9ef4f 100644 --- a/base/src/test/user_model/mod.rs +++ b/base/src/test/user_model/mod.rs @@ -10,6 +10,7 @@ mod test_defined_names; mod test_delete_row_column_formatting; mod test_diff_queue; mod test_evaluation; +mod test_fn_formulatext; mod test_general; mod test_grid_lines; mod test_keyboard_navigation; diff --git a/base/src/test/user_model/test_fn_formulatext.rs b/base/src/test/user_model/test_fn_formulatext.rs index 24df46095..67bf8e63b 100644 --- a/base/src/test/user_model/test_fn_formulatext.rs +++ b/base/src/test/user_model/test_fn_formulatext.rs @@ -1,10 +1,17 @@ +#![allow(clippy::unwrap_used)] + +use crate::test::user_model::util::new_empty_user_model; + #[test] fn formulatext_english() { - let mut model = UserModel::from_model(new_empty_model()); + let mut model = new_empty_user_model(); model.set_user_input(0, 1, 1, "=SUM(1, 2, 3)").unwrap(); model.set_user_input(0, 1, 2, "=FORMULATEXT(A1)").unwrap(); model.set_language("de").unwrap(); - assert_eq!(model.get_formatted_cell_value(0, 1, 2), Ok("=SUM(1,2,3)".to_string())); -} \ No newline at end of file + assert_eq!( + model.get_formatted_cell_value(0, 1, 2), + Ok("=SUM(1,2,3)".to_string()) + ); +} From 893284dc50e16243bd29d99f8b431448ddf0bb3f Mon Sep 17 00:00:00 2001 From: Brian Hung Date: Thu, 31 Jul 2025 12:05:34 -0700 Subject: [PATCH 15/22] Add combinatorial functions --- .../src/expressions/parser/static_analysis.rs | 10 +++ base/src/functions/mathematical.rs | 69 +++++++++++++++++++ base/src/functions/mod.rs | 20 +++++- base/src/test/mod.rs | 2 + base/src/test/test_fn_combin.rs | 21 ++++++ base/src/test/test_fn_fact.rs | 17 +++++ 6 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 base/src/test/test_fn_combin.rs create mode 100644 base/src/test/test_fn_fact.rs diff --git a/base/src/expressions/parser/static_analysis.rs b/base/src/expressions/parser/static_analysis.rs index 04edf5614..7a3f24fb3 100644 --- a/base/src/expressions/parser/static_analysis.rs +++ b/base/src/expressions/parser/static_analysis.rs @@ -654,6 +654,11 @@ fn get_function_args_signature(kind: &Function, arg_count: usize) -> Vec args_signature_scalars(arg_count, 2, 0), Function::Rounddown => args_signature_scalars(arg_count, 2, 0), Function::Roundup => args_signature_scalars(arg_count, 2, 0), + Function::Fact => args_signature_scalars(arg_count, 1, 0), + Function::Combin => args_signature_scalars(arg_count, 2, 0), + Function::Combina => args_signature_scalars(arg_count, 2, 0), + Function::Permut => args_signature_scalars(arg_count, 2, 0), + Function::Permutationa => args_signature_scalars(arg_count, 2, 0), Function::Sin => args_signature_scalars(arg_count, 1, 0), Function::Sinh => args_signature_scalars(arg_count, 1, 0), Function::Sqrt => args_signature_scalars(arg_count, 1, 0), @@ -1045,6 +1050,11 @@ fn static_analysis_on_function(kind: &Function, args: &[Node]) -> StaticResult { Function::Round => scalar_arguments(args), Function::Rounddown => scalar_arguments(args), Function::Roundup => scalar_arguments(args), + Function::Fact => scalar_arguments(args), + Function::Combin => scalar_arguments(args), + Function::Combina => scalar_arguments(args), + Function::Permut => scalar_arguments(args), + Function::Permutationa => scalar_arguments(args), Function::Ln => scalar_arguments(args), Function::Log => scalar_arguments(args), Function::Log10 => scalar_arguments(args), diff --git a/base/src/functions/mathematical.rs b/base/src/functions/mathematical.rs index 846e4e038..b4e7d8c99 100644 --- a/base/src/functions/mathematical.rs +++ b/base/src/functions/mathematical.rs @@ -1671,4 +1671,73 @@ impl<'a> Model<'a> { } CalcResult::Number(result) } + + pub(crate) fn fn_permut(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult { + if args.len() != 2 { + return CalcResult::new_args_number_error(cell); + } + let n = match self.get_number(&args[0], cell) { + Ok(f) => f.floor(), + Err(s) => return s, + }; + let k = match self.get_number(&args[1], cell) { + Ok(f) => f.floor(), + Err(s) => return s, + }; + if n < 0.0 || k < 0.0 { + return CalcResult::Error { + error: Error::NUM, + origin: cell, + message: "Arguments must be non-negative integers".to_string(), + }; + } + if k > n { + return CalcResult::Error { + error: Error::NUM, + origin: cell, + message: "k cannot be greater than n".to_string(), + }; + } + let n = n as usize; + let k = k as usize; + let mut result = 1.0; + for i in 0..k { + result *= (n - i) as f64; + } + if result.is_infinite() { + return CalcResult::new_error(Error::NUM, cell, "overflow".to_string()); + } + CalcResult::Number(result) + } + + pub(crate) fn fn_permutationa(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult { + if args.len() != 2 { + return CalcResult::new_args_number_error(cell); + } + let n = match self.get_number(&args[0], cell) { + Ok(f) => f.floor(), + Err(s) => return s, + }; + let k = match self.get_number(&args[1], cell) { + Ok(f) => f.floor(), + Err(s) => return s, + }; + if n < 0.0 || k < 0.0 { + return CalcResult::Error { + error: Error::NUM, + origin: cell, + message: "Arguments must be non-negative integers".to_string(), + }; + } + let n = n as usize; + let k = k as usize; + let mut result = 1.0; + for _ in 0..k { + result *= n as f64; + } + if result.is_infinite() { + return CalcResult::new_error(Error::NUM, cell, "overflow".to_string()); + } + CalcResult::Number(result) + } } diff --git a/base/src/functions/mod.rs b/base/src/functions/mod.rs index 3a8edf115..b026ab59e 100644 --- a/base/src/functions/mod.rs +++ b/base/src/functions/mod.rs @@ -118,6 +118,8 @@ pub enum Function { Arabic, Combin, Combina, + Permut, + Permutationa, Sumsq, // Information @@ -428,6 +430,11 @@ macro_rules! impl_function_lookup { impl Functions { pub fn lookup(&self, name: &str) -> Option { let key = name.to_uppercase(); + match key.as_str() { + "PERMUT" => return Some(Function::Permut), + "PERMUTATIONA" => return Some(Function::Permutationa), + _ => {} + } $( if self.$field == key { return Some(Function::$variant); @@ -906,6 +913,8 @@ impl Function { Function::Arabic => functions.arabic.clone(), Function::Combin => functions.combin.clone(), Function::Combina => functions.combina.clone(), + Function::Permut => "PERMUT".to_string(), + Function::Permutationa => "PERMUTATIONA".to_string(), Function::Sumsq => functions.sumsq.clone(), Function::ErrorType => functions.errortype.clone(), Function::Formulatext => functions.formulatext.clone(), @@ -1168,7 +1177,7 @@ impl Function { Function::Steyx => functions.steyx.clone(), } } - pub fn into_iter() -> IntoIter { + pub fn into_iter() -> IntoIter { [ Function::And, Function::False, @@ -1425,6 +1434,8 @@ impl Function { Function::Arabic, Function::Combin, Function::Combina, + Function::Permut, + Function::Permutationa, Function::Sumsq, Function::N, Function::Cell, @@ -1701,6 +1712,11 @@ impl<'a> Model<'a> { Function::Round => self.fn_round(args, cell), Function::Rounddown => self.fn_rounddown(args, cell), Function::Roundup => self.fn_roundup(args, cell), + Function::Fact => self.fn_fact(args, cell), + Function::Combin => self.fn_combin(args, cell), + Function::Combina => self.fn_combina(args, cell), + Function::Permut => self.fn_permut(args, cell), + Function::Permutationa => self.fn_permutationa(args, cell), Function::Sum => self.fn_sum(args, cell), Function::Sumif => self.fn_sumif(args, cell), Function::Sumifs => self.fn_sumifs(args, cell), @@ -1913,6 +1929,8 @@ impl<'a> Model<'a> { Function::Arabic => self.fn_arabic(args, cell), Function::Combin => self.fn_combin(args, cell), Function::Combina => self.fn_combina(args, cell), + Function::Permut => self.fn_permut(args, cell), + Function::Permutationa => self.fn_permutationa(args, cell), Function::Sumsq => self.fn_sumsq(args, cell), Function::N => self.fn_n(args, cell), Function::Cell => self.fn_cell(args, cell), diff --git a/base/src/test/mod.rs b/base/src/test/mod.rs index 3c2b0f052..29747bbd2 100644 --- a/base/src/test/mod.rs +++ b/base/src/test/mod.rs @@ -81,6 +81,8 @@ mod test_fn_round; mod test_fn_type; mod test_frozen_rows_and_columns; mod test_geomean; +mod test_fn_fact; +mod test_fn_combin; mod test_get_cell_content; mod test_implicit_intersection; mod test_issue_155; diff --git a/base/src/test/test_fn_combin.rs b/base/src/test/test_fn_combin.rs new file mode 100644 index 000000000..481b7f2c1 --- /dev/null +++ b/base/src/test/test_fn_combin.rs @@ -0,0 +1,21 @@ +#![allow(clippy::unwrap_used)] + +use crate::test::util::new_empty_model; + +#[test] +fn combin_family() { + let mut model = new_empty_model(); + model._set("A1", "=COMBIN(10,3)"); + model._set("A2", "=COMBINA(4,2)"); + model._set("A3", "=PERMUT(10,3)"); + model._set("A4", "=PERMUTATIONA(10,3)"); + model._set("A5", "=COMBIN(1)"); + + model.evaluate(); + + assert_eq!(model._get_text("A1"), *"120"); + assert_eq!(model._get_text("A2"), *"10"); + assert_eq!(model._get_text("A3"), *"720"); + assert_eq!(model._get_text("A4"), *"1000"); + assert_eq!(model._get_text("A5"), *"#ERROR!"); +} diff --git a/base/src/test/test_fn_fact.rs b/base/src/test/test_fn_fact.rs new file mode 100644 index 000000000..c6dd1f77f --- /dev/null +++ b/base/src/test/test_fn_fact.rs @@ -0,0 +1,17 @@ +#![allow(clippy::unwrap_used)] + +use crate::test::util::new_empty_model; + +#[test] +fn fact_basic() { + let mut model = new_empty_model(); + model._set("A1", "=FACT(3)"); + model._set("A2", "=FACT(-1)"); + model._set("A3", "=FACT()"); + + model.evaluate(); + + assert_eq!(model._get_text("A1"), *"6"); + assert_eq!(model._get_text("A2"), *"#NUM!"); + assert_eq!(model._get_text("A3"), *"#ERROR!"); +} From 3ef902840019d1e52297dd886f4246758bc6b279 Mon Sep 17 00:00:00 2001 From: Brian Hung Date: Thu, 31 Jul 2025 12:25:34 -0700 Subject: [PATCH 16/22] test coverage --- base/src/functions/mathematical.rs | 6 +++- base/src/test/mod.rs | 7 +++-- base/src/test/test_fn_combin.rs | 44 ++++++++++++++++++++------- base/src/test/test_fn_combina.rs | 38 +++++++++++++++++++++++ base/src/test/test_fn_fact.rs | 25 ++++++++++----- base/src/test/test_fn_permut.rs | 43 ++++++++++++++++++++++++++ base/src/test/test_fn_permutationa.rs | 38 +++++++++++++++++++++++ 7 files changed, 180 insertions(+), 21 deletions(-) create mode 100644 base/src/test/test_fn_combina.rs create mode 100644 base/src/test/test_fn_permut.rs create mode 100644 base/src/test/test_fn_permutationa.rs diff --git a/base/src/functions/mathematical.rs b/base/src/functions/mathematical.rs index b4e7d8c99..c06e97922 100644 --- a/base/src/functions/mathematical.rs +++ b/base/src/functions/mathematical.rs @@ -1710,7 +1710,11 @@ impl<'a> Model<'a> { CalcResult::Number(result) } - pub(crate) fn fn_permutationa(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult { + pub(crate) fn fn_permutationa( + &mut self, + args: &[Node], + cell: CellReferenceIndex, + ) -> CalcResult { if args.len() != 2 { return CalcResult::new_args_number_error(cell); } diff --git a/base/src/test/mod.rs b/base/src/test/mod.rs index 29747bbd2..e02b526ad 100644 --- a/base/src/test/mod.rs +++ b/base/src/test/mod.rs @@ -75,14 +75,17 @@ mod test_even_odd; mod test_exp_sign; mod test_extend; mod test_floor; +mod test_fn_combin; +mod test_fn_combina; +mod test_fn_fact; mod test_fn_datevalue_timevalue; mod test_fn_fv; +mod test_fn_permut; +mod test_fn_permutationa; mod test_fn_round; mod test_fn_type; mod test_frozen_rows_and_columns; mod test_geomean; -mod test_fn_fact; -mod test_fn_combin; mod test_get_cell_content; mod test_implicit_intersection; mod test_issue_155; diff --git a/base/src/test/test_fn_combin.rs b/base/src/test/test_fn_combin.rs index 481b7f2c1..d45cabbf0 100644 --- a/base/src/test/test_fn_combin.rs +++ b/base/src/test/test_fn_combin.rs @@ -3,19 +3,41 @@ use crate::test::util::new_empty_model; #[test] -fn combin_family() { +fn combin_comprehensive() { let mut model = new_empty_model(); - model._set("A1", "=COMBIN(10,3)"); - model._set("A2", "=COMBINA(4,2)"); - model._set("A3", "=PERMUT(10,3)"); - model._set("A4", "=PERMUTATIONA(10,3)"); - model._set("A5", "=COMBIN(1)"); + + model._set("A1", "=COMBIN(5,0)"); + model._set("A2", "=COMBIN(5,5)"); + model._set("A3", "=COMBIN(10,3)"); + model._set("A4", "=COMBIN(7,1)"); + model._set("A5", "=COMBIN(10.9,3.2)"); + model._set("A6", "=COMBIN(3,4)"); + model._set("A7", "=COMBIN(-1,2)"); + model._set("A8", "=COMBIN()"); + + model.evaluate(); + + assert_eq!(model._get_text("A1"), *"1"); + assert_eq!(model._get_text("A2"), *"1"); + assert_eq!(model._get_text("A3"), *"120"); + assert_eq!(model._get_text("A4"), *"7"); + assert_eq!(model._get_text("A5"), *"120"); + assert_eq!(model._get_text("A6"), *"#NUM!"); + assert_eq!(model._get_text("A7"), *"#NUM!"); + assert_eq!(model._get_text("A8"), *"#ERROR!"); +} + +#[test] +fn combin_mathematical_properties() { + let mut model = new_empty_model(); + + model._set("A1", "=COMBIN(8,3)"); + model._set("A2", "=COMBIN(8,5)"); + model._set("A3", "=COMBIN(5,2)"); + model._set("A4", "=COMBIN(4,1)+COMBIN(4,2)"); model.evaluate(); - assert_eq!(model._get_text("A1"), *"120"); - assert_eq!(model._get_text("A2"), *"10"); - assert_eq!(model._get_text("A3"), *"720"); - assert_eq!(model._get_text("A4"), *"1000"); - assert_eq!(model._get_text("A5"), *"#ERROR!"); + assert_eq!(model._get_text("A1"), model._get_text("A2")); + assert_eq!(model._get_text("A3"), model._get_text("A4")); } diff --git a/base/src/test/test_fn_combina.rs b/base/src/test/test_fn_combina.rs new file mode 100644 index 000000000..74a71edf4 --- /dev/null +++ b/base/src/test/test_fn_combina.rs @@ -0,0 +1,38 @@ +#![allow(clippy::unwrap_used)] + +use crate::test::util::new_empty_model; + +#[test] +fn combina_comprehensive() { + let mut model = new_empty_model(); + + model._set("A1", "=COMBINA(0,0)"); + model._set("A2", "=COMBINA(0,3)"); + model._set("A3", "=COMBINA(4,3)"); + model._set("A4", "=COMBINA(3,1)"); + model._set("A5", "=COMBINA(4.9,2.1)"); + model._set("A6", "=COMBINA(-1,2)"); + model._set("A7", "=COMBINA()"); + + model.evaluate(); + + assert_eq!(model._get_text("A1"), *"1"); + assert_eq!(model._get_text("A2"), *"0"); + assert_eq!(model._get_text("A3"), *"20"); + assert_eq!(model._get_text("A4"), *"3"); + assert_eq!(model._get_text("A5"), *"10"); + assert_eq!(model._get_text("A6"), *"#NUM!"); + assert_eq!(model._get_text("A7"), *"#ERROR!"); +} + +#[test] +fn combina_mathematical_identity() { + let mut model = new_empty_model(); + + model._set("A1", "=COMBINA(4,3)"); + model._set("A2", "=COMBIN(6,3)"); + + model.evaluate(); + + assert_eq!(model._get_text("A1"), model._get_text("A2")); +} diff --git a/base/src/test/test_fn_fact.rs b/base/src/test/test_fn_fact.rs index c6dd1f77f..230d99aa5 100644 --- a/base/src/test/test_fn_fact.rs +++ b/base/src/test/test_fn_fact.rs @@ -3,15 +3,26 @@ use crate::test::util::new_empty_model; #[test] -fn fact_basic() { +fn fact_comprehensive() { let mut model = new_empty_model(); - model._set("A1", "=FACT(3)"); - model._set("A2", "=FACT(-1)"); - model._set("A3", "=FACT()"); + + model._set("A1", "=FACT(0)"); + model._set("A2", "=FACT(3)"); + model._set("A3", "=FACT(5)"); + model._set("A4", "=FACT(170)"); + model._set("A5", "=FACT(171)"); + model._set("A6", "=FACT(3.7)"); + model._set("A7", "=FACT(-1)"); + model._set("A8", "=FACT()"); model.evaluate(); - assert_eq!(model._get_text("A1"), *"6"); - assert_eq!(model._get_text("A2"), *"#NUM!"); - assert_eq!(model._get_text("A3"), *"#ERROR!"); + assert_eq!(model._get_text("A1"), *"1"); + assert_eq!(model._get_text("A2"), *"6"); + assert_eq!(model._get_text("A3"), *"120"); + assert_eq!(model._get_text("A4"), *"7.25742E+306"); + assert_eq!(model._get_text("A5"), *"#NUM!"); + assert_eq!(model._get_text("A6"), *"6"); + assert_eq!(model._get_text("A7"), *"#NUM!"); + assert_eq!(model._get_text("A8"), *"#ERROR!"); } diff --git a/base/src/test/test_fn_permut.rs b/base/src/test/test_fn_permut.rs new file mode 100644 index 000000000..cdf6751f6 --- /dev/null +++ b/base/src/test/test_fn_permut.rs @@ -0,0 +1,43 @@ +#![allow(clippy::unwrap_used)] + +use crate::test::util::new_empty_model; + +#[test] +fn permut_comprehensive() { + let mut model = new_empty_model(); + + model._set("A1", "=PERMUT(5,0)"); + model._set("A2", "=PERMUT(4,4)"); + model._set("A3", "=PERMUT(8,3)"); + model._set("A4", "=PERMUT(6,1)"); + model._set("A5", "=PERMUT(8.7,2.3)"); + model._set("A6", "=PERMUT(3,5)"); + model._set("A7", "=PERMUT(-1,2)"); + model._set("A8", "=PERMUT()"); + + model.evaluate(); + + assert_eq!(model._get_text("A1"), *"1"); + assert_eq!(model._get_text("A2"), *"24"); + assert_eq!(model._get_text("A3"), *"336"); + assert_eq!(model._get_text("A4"), *"6"); + assert_eq!(model._get_text("A5"), *"56"); + assert_eq!(model._get_text("A6"), *"#NUM!"); + assert_eq!(model._get_text("A7"), *"#NUM!"); + assert_eq!(model._get_text("A8"), *"#ERROR!"); +} + +#[test] +fn permut_mathematical_relationships() { + let mut model = new_empty_model(); + + model._set("A1", "=PERMUT(6,3)"); + model._set("A2", "=COMBIN(6,3)*FACT(3)"); + model._set("A3", "=PERMUT(5,5)"); + model._set("A4", "=FACT(5)"); + + model.evaluate(); + + assert_eq!(model._get_text("A1"), model._get_text("A2")); + assert_eq!(model._get_text("A3"), model._get_text("A4")); +} diff --git a/base/src/test/test_fn_permutationa.rs b/base/src/test/test_fn_permutationa.rs new file mode 100644 index 000000000..a9921664a --- /dev/null +++ b/base/src/test/test_fn_permutationa.rs @@ -0,0 +1,38 @@ +#![allow(clippy::unwrap_used)] + +use crate::test::util::new_empty_model; + +#[test] +fn permutationa_comprehensive() { + let mut model = new_empty_model(); + + model._set("A1", "=PERMUTATIONA(0,0)"); + model._set("A2", "=PERMUTATIONA(0,3)"); + model._set("A3", "=PERMUTATIONA(5,0)"); + model._set("A4", "=PERMUTATIONA(10,3)"); + model._set("A5", "=PERMUTATIONA(5.9,2.9)"); + model._set("A6", "=PERMUTATIONA(-1,2)"); + model._set("A7", "=PERMUTATIONA()"); + + model.evaluate(); + + assert_eq!(model._get_text("A1"), *"1"); + assert_eq!(model._get_text("A2"), *"0"); + assert_eq!(model._get_text("A3"), *"1"); + assert_eq!(model._get_text("A4"), *"1000"); + assert_eq!(model._get_text("A5"), *"25"); + assert_eq!(model._get_text("A6"), *"#NUM!"); + assert_eq!(model._get_text("A7"), *"#ERROR!"); +} + +#[test] +fn permutationa_power_equivalence() { + let mut model = new_empty_model(); + + model._set("A1", "=PERMUTATIONA(4,3)"); + model._set("A2", "=POWER(4,3)"); + + model.evaluate(); + + assert_eq!(model._get_text("A1"), model._get_text("A2")); +} From 256134eb1c43438bff5b17932c9807b3f8873f64 Mon Sep 17 00:00:00 2001 From: Brian Hung Date: Thu, 31 Jul 2025 12:35:02 -0700 Subject: [PATCH 17/22] integer overflow underflow tests --- base/src/test/test_fn_combin.rs | 14 ++++++++++++++ base/src/test/test_fn_combina.rs | 15 +++++++++++++++ base/src/test/test_fn_fact.rs | 15 +++++++++++++++ base/src/test/test_fn_permut.rs | 15 +++++++++++++++ base/src/test/test_fn_permutationa.rs | 15 +++++++++++++++ 5 files changed, 74 insertions(+) diff --git a/base/src/test/test_fn_combin.rs b/base/src/test/test_fn_combin.rs index d45cabbf0..dadbe01a3 100644 --- a/base/src/test/test_fn_combin.rs +++ b/base/src/test/test_fn_combin.rs @@ -41,3 +41,17 @@ fn combin_mathematical_properties() { assert_eq!(model._get_text("A1"), model._get_text("A2")); assert_eq!(model._get_text("A3"), model._get_text("A4")); } + +#[test] +fn combin_overflow() { + let mut model = new_empty_model(); + + model._set("A1", "=COMBIN(1000, 500)"); + model._set("A2", "=COMBIN(10000, 5000)"); + + model.evaluate(); + + let a1_result = model._get_text("A1"); + assert!(a1_result == "#NUM!" || !a1_result.is_empty()); + assert_eq!(model._get_text("A2"), *"#NUM!"); +} diff --git a/base/src/test/test_fn_combina.rs b/base/src/test/test_fn_combina.rs index 74a71edf4..bfcfcd3f8 100644 --- a/base/src/test/test_fn_combina.rs +++ b/base/src/test/test_fn_combina.rs @@ -36,3 +36,18 @@ fn combina_mathematical_identity() { assert_eq!(model._get_text("A1"), model._get_text("A2")); } + +#[test] +fn combina_overflow() { + let mut model = new_empty_model(); + + model._set("A1", "=COMBINA(18446744073709551615, 2)"); + model._set("A2", "=COMBINA(18446744073709551614, 3)"); + model._set("A3", "=COMBINA(9223372036854775807, 9223372036854775807)"); + + model.evaluate(); + + assert_eq!(model._get_text("A1"), *"#NUM!"); + assert_eq!(model._get_text("A2"), *"#NUM!"); + assert_eq!(model._get_text("A3"), *"#NUM!"); +} diff --git a/base/src/test/test_fn_fact.rs b/base/src/test/test_fn_fact.rs index 230d99aa5..2d8d3f44e 100644 --- a/base/src/test/test_fn_fact.rs +++ b/base/src/test/test_fn_fact.rs @@ -26,3 +26,18 @@ fn fact_comprehensive() { assert_eq!(model._get_text("A7"), *"#NUM!"); assert_eq!(model._get_text("A8"), *"#ERROR!"); } + +#[test] +fn fact_overflow() { + let mut model = new_empty_model(); + + model._set("A1", "=FACT(170)"); + model._set("A2", "=FACT(171)"); + model._set("A3", "=FACT(200)"); + + model.evaluate(); + + assert_eq!(model._get_text("A1"), *"7.25742E+306"); + assert_eq!(model._get_text("A2"), *"#NUM!"); + assert_eq!(model._get_text("A3"), *"#NUM!"); +} diff --git a/base/src/test/test_fn_permut.rs b/base/src/test/test_fn_permut.rs index cdf6751f6..f79a20349 100644 --- a/base/src/test/test_fn_permut.rs +++ b/base/src/test/test_fn_permut.rs @@ -41,3 +41,18 @@ fn permut_mathematical_relationships() { assert_eq!(model._get_text("A1"), model._get_text("A2")); assert_eq!(model._get_text("A3"), model._get_text("A4")); } + +#[test] +fn permut_overflow() { + let mut model = new_empty_model(); + + model._set("A1", "=PERMUT(100, 50)"); + model._set("A2", "=PERMUT(1000, 500)"); + model._set("A3", "=PERMUT(200, 100)"); + + model.evaluate(); + + assert_eq!(model._get_text("A1"), *"3.06852E+93"); + assert_eq!(model._get_text("A2"), *"#NUM!"); + assert_eq!(model._get_text("A3"), *"8.45055E+216"); +} diff --git a/base/src/test/test_fn_permutationa.rs b/base/src/test/test_fn_permutationa.rs index a9921664a..d8592ae10 100644 --- a/base/src/test/test_fn_permutationa.rs +++ b/base/src/test/test_fn_permutationa.rs @@ -36,3 +36,18 @@ fn permutationa_power_equivalence() { assert_eq!(model._get_text("A1"), model._get_text("A2")); } + +#[test] +fn permutationa_overflow() { + let mut model = new_empty_model(); + + model._set("A1", "=PERMUTATIONA(100, 10)"); + model._set("A2", "=PERMUTATIONA(1000, 100)"); + model._set("A3", "=PERMUTATIONA(10, 100)"); + + model.evaluate(); + + assert_eq!(model._get_text("A1"), *"1E+20"); + assert_eq!(model._get_text("A2"), *"1E+300"); + assert_eq!(model._get_text("A3"), *"1E+100"); +} From d817ebe86c4d7b718f1d5cbf4e82e2cbcb0ed540 Mon Sep 17 00:00:00 2001 From: BrianHung Date: Sat, 31 Jan 2026 12:13:41 -0800 Subject: [PATCH 18/22] Fix formatting for CI --- xlsx/hello-calc.xlsx | Bin 0 -> 51603 bytes xlsx/hello-styles.xlsx | Bin 0 -> 3881 bytes xlsx/widths-and-heights.xlsx | Bin 0 -> 3853 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 xlsx/hello-calc.xlsx create mode 100644 xlsx/hello-styles.xlsx create mode 100644 xlsx/widths-and-heights.xlsx diff --git a/xlsx/hello-calc.xlsx b/xlsx/hello-calc.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..d2b38ff43ee1adacc8331bacc45ec7ab0662792c GIT binary patch literal 51603 zcmZ^J2RK~M+x8M-5iC}3yL#_LBU541S-P= zfhd96M+kQ}A3HZ6OTz#UJ8yGAe^-~T^g*`{VMx?=NL=qjD1(%tk@EMEe4BW~Gf;Ia zocbzlO3(k?4Y%rboFXzs%Cv9j>s|E*I_+;m)O>0V5jE4{$EG+I_^ub9tZV9f_E1z= zrBh?K$*AT)b$usJ;&V!$Bfc{8eOqC4kCvYdPwPeC>844ubBURkHOJ};tT1BVEwxkg zp4^emY#w~_b5v%+%h7xHUD@0mDvHYkt&$=TfveeUF=vF8Vj{vNgc+ zfpZsya5Nec7=riB`TDyoA5}%_b0ap`aUhh3&qgXynBZ@Af;L^nkix8)9|{Fm^)EgV zyA>DEaMbr%0Yq7%+zdK=*)%)QI-<0Dz~X+lXUu=GHC=R-wj}z1<;9MK7BU0_>KK}j z<61kKM_JAeeK-}nSjug_ecb-v=Uc`3AR_>ntMDxli2T3K*T&t;?w<*#BAtPq*t2xZ zqp|30Ml2dWp5;#3D^7V zWPeBs2s)+sdMoPf!~LBUJkpmG{8L1nCAg|(Go4vE>CVLN#tiSczu+^MJnkQi+JtCp zRQ|lroyFiEz1M~LfRA9A6_7~So}peBj#Fs>*+#&NC4|M9QgTxw{M5YP1>AzP%ZA#!Q*Nie4zr9Wxx2{RR@ zexQuLXKNEEbCz~LIOJB3=s3NySh2%p zY#b}N?#o!z^nW(QD0ngD#vWV#=U6!m|5;LWv5!D4%`1%C(_7^Jkz?P6G*@L_%GCT~ zXHe-x(so%<)S!^`T#giB-+msR)qGwKzHUd;$cxDy?^Jy-x_>3%^zC8cQa*?1gWRo8 zIUmjUHVOWNr~kcuZv#A8dfB=B&%I0Y;jYwgpiL7O1S0-VjNlEjDs@n&Q}|B!_5C^UM>) zDL6Gw>C6snZ#&W5wL1;}k%coEQF&yHn4SyLJN&XQ;o_LRxKp1sj5e1Z#SPHeLb~!MkhI0S^RRcCS$lv9E$enLZ_Yn$c$OpbR0U7{!J6L(y*&6tGIl4Xd zzEOY#MuD^g1_5oiMQkqU%h1|r1PSlrpMmthDdG~>#g8rxbp5myed%~wQn6@q&sNlf zRz*+E+4s5Rt)z3;u-jeMeKT2vB*7_GFa0R%EAl89c34-7Bx;z`R^c=yZh6g|d`uf< z&4vw$GDRg7Nk9F^`flUC|D|GGpvw^buyZ4SP5rp@;?{pm>|bF@kVD@5IUrkYU{)mm z_S+}G#qJ-6Ul@FG0?2ON=1bUqGna@FNlh04Z_bnYKBMH4==FNHFt)kD)uTQq+-)M5 znpax>)U;3X>8;I@+$n1BHZ%Tv1|~CRHaYuA<^k7VUy@Nc6H*%u1+AWzH4Ox>E4Y$r z;H%tO_YMUY29b9>B(N*7oYsNA68}a=$L&+K^U9#^sJ1uUL?~0YLk;t(mp=YWeLu5E zax6Kw=yjJ41vub)Q7@WtKb}=%5@_fy*-Ear;U(UnK1i6&4Ja_p(;i+yuh9ACb)31pI<|j<{nG0 zH4q}ZR>opgXp+jr4PY®gN=wCQc7|xn1DT%~+x#e*2$QkA4q+I2Dab6K0jyQCO zD%5B%-|m_!(Z+WQn1F~65mJ&39tKZl>(t08?vng>u0kH&&A$XVdJphL_Ajpd+`XKw z-QAsUwiib4%Z)Svwpf=;XPF_dXa9l}XYt`8O;6D+D$c3ALy{*!xkiX+%I4|BrJzvX z`ct1`DI0c%VB*z}pyw6@HkXCb#|!%lx4;_x3=+4K1cKpT${tB%LXyrQi4muHX0-a* zPCV*l>(HvssJ#0ju{s=rKNZHHNIH5*8iSn?^mvSC!^*hgXPCI+(j$znLO$b}c)Sv$ zk@d0oG`hgK?hRQy%^%~4rKIr-u5_KFg!}K^3qdmydt}+W4`U_i+iz)q0L>jhoTLA*u>ZQO|Af8P)ZrT?#B2u<6`wD&GuR|Zw9CQUCqYX*Cl+B3 z+9F`z7XQHg4Ta%f>HOuHD1g6wm6qoZ5`2AzcK8a0oHlC&^0J>`^!bb`l(jb1+gQ7G)2s?oYeG7t+Lm# zp>kQo10jPY)3P@9eIHG_Wyjtene5FC&E4U<7(eO4%jNW8H#ZS?7TcA+-x81h|LaHk ze|MUL@6?7<7Rbab;(nXR* zs(mwoSRcDWGDv}9MPn(de9Pg~*Q(9R2daGyI$ zx<0?Ymbba%s zgSD{B@%HQ0;r7dMmaFZhtD~6f%lzxzu&dRY>#3!yIrsL$QIXK#!}X>j9SQ zlg6-9ps6SS>IfLB{rYH)Z_o02Z|eGFe5vMo+wyu??rN+3`mj;%a^w1}`1<5nPV(;E zPL}JRx&T)0;!5Zuv;C_5^zIeVZKM5iz#TA{dA$^NakSQceU)@^*cf&VnA*N_zusE9 zI$yfl8NNQLx%Tb4+V{QsQPX}o6?TT0z06#7&AZ;|3A^gKKAhdYKAO9}nA5#JXSv!p zzDjDpSiL?QzFzBTzZz$$&i@QdH|*2Z(KayLQdrB?b<0NkY2)?b_2pXoY5B(WDNDOR z`{jB2^_pDBrOQ?Os?pUx{?&Te;o5c8!5fYeyToHccA(PojdfqfK3#W(=DqQ7zih=B`Z(h9{HV=K^@nKQF0K?{`Cq zyJr2{qixJTEh*LsF1lNp(rh=5yp2D8|N^k5~X$ zRnqaJlW#HSgVF9GO-qmeXVI*ys+#T_034koR|CobpH}1H`0>&i)SA!V@f7&pGwPE8)IZpUzX%mUlx^Nuf0Dn>HO!{qx#o2ZFk>9 ztX7*EaUKu8#ycH->x7B1H!^%6`R0$+(%b&1a(g2(#_UPg>t@r11 z^AY;+Z=l~3Z{2t*J^0+1DwFx#$SdFRx#3ri@Clw)?(hk2RYLd$e^d(d3r<$*@eB4< zdbmXRI2mV78=6pOju|>oXMQ#Gr_Ss)jHS-}Y?wuz`O&a~I+^oSG3^U+t_eO266ovgnKVue+jooz0yKitw;B zVGxMr1yVkEw>A7^IXbkZ$ze4TC=KH-$G3xHg+ie~b4fIFpauY`2O8XO8i-?sEMlAD ziA|{~3;Vm2dfi1t1nuLqXdl)qYcUqQ#Zyv;b9Upql2iVS`w)xvRDXoUHkAojTR)zl z77?j-0+5;Sm9?TKpp=C?T}rX4BILYCttdv|@*JxKejq}wp`#ULOG#Nch{fg`H(6qh zoogqr+DZvT-#gbionAVPylLEe9eQRg*u!en!khj#w4HH)|9I;)>lspzVD)sQS|h&) zkn7Jj^GPY)8!aMQc_>C;XWMShJy)#0N3B8B(o}g3 z3D-AjY4a=Zs5O6!Dhp|8Q!eMi*{vqqI@8I`!^fV7I!@i4_klvTKM9kbI|WjO7Xk2) zvW^y}euwR?Gr^a#c-Z^x4I4RDX8`Epjrr2P7X>7qD>`y|c8?Xb`o=eeV8HB$p2sMC z3{n59a1#o0O0Eha9M9*}ls`YDbzx16d#$bK&FiwHijya;DS7_9dE0(>cgPYjD@PRT z7dqBKcE|x+JT;a7w0{?8cZishL2a50hue`b*3Tkq8p|4RMFDACXNP5|*Bwa$mTNt{ z`CxwsmhiCp`c~GIMk_^^A5=Xx0G23xEJ_A^~HqAKU@-?hqO7@~e0>H+>u$ z;$_X+A85^bkewCVk&^{B<>b6Fq(4^LS{pEPe~wAi~k9`MXO8Li?ikMR;ih5|8_<9Ok%$TxMQedd8)2-_l{v7r6-DC`gK&1ch(~uE@|Zu8xbAYp+WZR{^2)~z8U^8F z3Ud7G!a%#_$7iL02fc%TD& zyY$Vx>*jJanuG}1h`a1mSxTDop8CFFQp0?qn$J?zcHe0Xpe$hh;%PXoHwORlgOY*K zmigl$lcd~@*s`X-f8Cz0axXb8VFF2O0S$M^u)r0 zt+kaeB0iCKj!66P{ZYUW#)cvNRj9nVU{}gN=!y1!$AApG&PZzaxNj;LHNO8=m%oTd zv=^!aa%|_GN7%ucZVDHX2WmCAdQ^O%wi{gt5fK*(j{$tp&^Po< zd|Ft_01LbV-f!;f_*H(p_lKj=+80CnuK?`vC5rz94h&>v9q%*-EGA7fdirud4RC=d zytDu5ja|yxzO|bcF4tTCwredk41`l)fDb1_ayN!++-FV=053bR)T-v{@^3qAw|xGM z^_?zjznNmzw8HKJp}f&s(C&T9hFd+`!8}@>muRW$j~6_e+r1z zEpCueEv|f7E73o_f7}nF9I1Iz`^CxS#5@pBl*Dd{ptQKLeFN^Ycucq((6HY5Uugyqz#}luj}*GisJnK)t%IsB_Y+yiwAFsDgiNm7|zrc zW{!O2BO^JIxv{IZ@m7eNNYtFH=G>yXr@WL5CE;cQ>fhN4{47VWXTG$2r5?IeJ z;!8bpb=8}~E#!$>bi@eXZ+_6=A-?>;YzwGiiUy=@muY35QKEYTp+u(ls>}1+Ksk$! zl8GrRW!>-#2HQXmHe*FGV@3dJ)Wzpk2-^$bF#IG;{_Oe1jry%vnXkRASR0gN!SbCU zrsY37GA!{5R)KC6_enNV;q65KD6{b7g!2Y%9Yb$pW!=Lmg5%IC1k!14$16Amu+{yY zD3A#rwqFN8czp%77~sx1L&J9((Cl9eQ1%WnT89r?623nz4NM-nYTx&C?h1@Lx|T$M*VxNbDU<-$A!PZVP(M|!0qKd0y7-TRT`hVU(PLgqZ8lsF${TFVI$&q{bSW0ljcNo zDZM8IQVjqJ@#Il)v}|$aYZ0GIR`i@c=l8EMliV zv+GsF2%>#Pbnm2U0Vha@oKvpiXG!0ak~C1mCJo5wea9E-?0xNI;do98 zYHf~4TV_pn>9l>K32(c^Euc5bQoG#czp-Q;6WP~+C1vv9+QLt^1&%KoPDT5b4y>%N z`!7G7^*JHX{T?FF%rVbD!%iw$S+le-XTIGc1-#yD;sr?gOGFuz#oeC7P*f;ftGZe( z?AfHS)D}KnY?LJ>kEP-ytsQ?hWVH;*sm-MM$NFohX!I>oIZJ%( zeIX!kdPPhblOLE9mSUU6F>}zuPiULnAIf+&Q@N?3kg9EmLz?Qs8?Tpa3y5+)-}wF1 zrJ?LGtPeZv2VVrRls!v1d|cgAy01KCa~#^t+*oSR%*=CS zR^20T{Z@!4uFlb1t2J}O)A53Ps#v@=eZzC^l%HRo;bnDkQagy7jVc={jCOF?{YZ5S z-hXo@_#kq$c%y8P6?&xFLY2vhAJ>6xdrAzNG_8n1r5pCfQ0PjptKLT5$l(XesxBNB zsHkrRGn3OVgb_&s^4z+>m&d7k2YCi?3Vx9$Xr>DL$1p$=)HrLpkkL4+msit@b;xpW zs{wA`uvwT@6iudID_o24+_2E(PIT~0Q?ugn-=a0~mBU_4Tehpxq>Ds-PslW)pYn^K z9cu$K-INt7L~&}x4Ng?L3~8sZF@FuEdJwf8($on=+a5 zzw|nk0O*zAz73#U6i|vWuIW(f&+Wi6M+;R23m;N(9=%&%aIT8trG(J@?NDNzTj@}O znt55VPHF*!vI4Bi6*srP{ZacFPzV8cdj}TI+`5wX;TGWkO&$veu*>0A1=p;KwqbY$IqbW5qfgiJPM3h`=a~)X1Z^OV( zPj)nVTvyejy`pXbV>fesmhZAMyei#`tnSGlaBXI$Np@cie5~INXf3UoZ|diVAj@i1 z!oA|hNgA79j;T1Z!~J`Jq%R{M4;v&4j=g&cM5n{}aQ`{rcx%(eUB&<>jbG@|U7AS% zxwZ|s6b1$=$bkgh07O58CvOz{+|cY7EY83hPG2@&bxOFvi5HW<%(|gx&1M#tdJ@~c z%C55aSv$9+j)G^@BNwY7PR%t+p)L3O`w}ZO7ZQBinv--OwE}&6iQTOzIbBJIm#3Bz z)2x1=bjyuG4k@O=%1!!ltZRd`;Rd|SJ%CttAbSBz)W3%=mN84iB8?+HnHB9mX52#F zrBn@KJX#3CM8Bqtu(M=j+M$arHq=<={&U~-Ul+GzZ+ZHBehN!0N>A$=#mWEDcTRZl z>L0u5NNQD8V1V`5VR=TTnIcZS{O3~A%VH2!)%1GoDEZ*(Ob}Ok9)uh2=@S8hsB$ri z#}!ll30_{0x*fW`!L5%jrkwqR9VNW!_Csv?5tsZv;TK@2p_4nWm%5#Y*7O``QXcmKW2~@uKxkPM!+POqre*L*b zLjL%9mu{RK$>JJb7-n(pP8iA38Z->Ew8j`l@_mgx4D)@BKaAwZns^xI$J&E1lI1m} zuuE#5D{-HEgG&kDeD6yM|NOK|iNO5&ONrq8@k@!&{QXOb@O;WEiHLl$D~YIlgRA#{ z^)4L0%W_>hugE^VbY7P&zjWS|UAuJNk>$E_K9GHS<$NMre&u{2yLROalHJbDQ( zK%Rfa8+jK}hU+jd>y0Dsk>3ZRZyt65kt{rw4)6Xe#v&hi{1WnR4x+idE-uC)Z*%mL zvOwWni)f@G#2(*aT6P>)+$}!Epw7Pg(04kAL4^Q3D$|dzL^BnGjQ#ANeedqrkS0ho{Yug4>eK^X^gpUcTRk*m35<8Q#U*!r6l} z_m;!Hoh82w%Gqt-jkK|CpfnJ+DOrY++myWRC-+y<{^_p?f)E6;H|^>YV?pJamXR%{O{n&?Z03I z$(!(qk^3foe&THbVdYMHq+z#SHr04NchZ9lrM3_lTMZ0W zYo0n!Sn5ey=sq|hSfX1gD68(FjaulwbI66W>$w4g$tuWgOLC&y*S#_AnZ@BIZysz( z{&EDuEm`8zFu&wT27}Tid~Do8Ot^`6Yb=2${2cn3UpHF!^J6&P3*K7DBG#5UF;+tu z!9$=)jh|3w%{IH0cGBCQLpxJAsgoW>CyY*mda&BXRDf&iBE^gvHj(NNPNvlEnLvC< z<6e3!aUkne<#LlMps{8hnUCBm43khHAN z_)6LspEx2z(bGq<3%G)10M*7*X@1cDFA?HkV+ii9P~uR<=eT0A+_vTQPpLF!5Vc<2 zZ9lb)6Va#u%Edu9ogco z9(L0D(lfId{Rouq(68>P+VO2bn*9iHE8bE#uXGyrr zdmoovzt)9OK)RN!8bx%jOj(vll{+x#zp6M$@mIbKh)E!|X4wpA6q&lvmWR@Aw_rw> zS#Vhk0oDFq%*UPPZW4KNA06317aM(GnjItM`AGs%Nkohrgq{XR+ETCtMDVIu2O=mq zWoR=qv)hj_GJ%-rV3DsSJbkcA_dL@}#XQr?)0eqj{AM;d_R2q57ZtE4 z>PS6nJ?7N}unt-PTVHe;l^+FCt8zhwyO2(zp6PbNo<7}`O;XauZ@ChO-dX29A0&&YSDJT*6Qw{< zdYTXDolJ$Hczx$iV3}L?LTNl-7ky`AW2PsAV;2H{Ar`B8r1)D7ZBcUzfzut03xS~> z#|we%9Sd%3E8jyKQ_Oa38dDT^-p%zw{pVfT@izS!_OF1bI(`n&YI-r;g6TU0__=r5 zv(PHyUoHp1V@}T5YDdpWC>@I9W;4 zc$M_(o<0LL-d|ghG$)-2M&S$Hc`Ugj#l0ViTGm{%muc(wA_$hbNEOq-(h&qSLNXI5 zd>k?$>6r#n5>iV~sOyO-quq6ul*S-uVuw{$7qVgEDG)aaju!{`-&-lX+lw!DNE!xG zjz2)$z17d3SZ4-A;u=1=AWHdLzv2r~k`xNPl~R-D(Pw%PFZLl6r2nfQs}!<13YiM!BFLAq2EJB1RaF7e?eCp$k&pBNW6i9J7Cs zo(HI5teE?0udwNnk(?fkC}Q;Vi9=DV5`>d5yY=gVaL~(9t0PhG&P_WMYGy#|PW|y( zLcAruHRkO!k2H)(iNq@j*HZ?u{NXXncTwQa^a`fxr_%GkFR@%JK{zDAoD91}WNi{) zrBYVxo%jWn?l0py$lD@4it@LL`&7Pujt^&6p;QK8rQ#R5ORgV=sP2kvYzmR#dfH51 zwcNC6_a+rm10)^iTZ8Z4ktoSiNyEI{R~NTM4l60~?D;l|oXVgRvWzp7RJAxXS=ocu zv1(d$nnmoIe#`9lSCurh2sEuZ%ECEDQwgdFt{HhC#1Wy&B5C;2NXqf1)F#461&Vt- zzp*-6qrmA{X2-Eh$>gGrr%Wek{^>vuMvbQhRSgj5P1IRugR0J806tpf;*0q|po=%} z?WfL+IOszT@D_t!mdf*kNr$^e1Ik8ilRXe;(S;~uN(H8kn25k^ZM;Il{jL$E7W4-) zajv{SLy9TfVCDAu!n_Phg^Id1hHM9EI>`ysHrm-yUsf>oLUdx(pA}5v*z8sgMMrqJ z@y`CGH8>xHBr>!!{Be*Y7k?MW?NEZF3?V9CJQzK0&1%i+Zr*%@Do}$)$*R=TM2geY z(@=`h)Mr-0?S-VDQ`ggU3IbQVO!h+E4+ZVFhrM~jsL&EBHsvN!+!PX1vT;fnmLyi> z&*EY=ep9LnR^Cut!E{V6ItX}9!mSY-TS(t_w?awzKK2Rb6$RsW1d)}Ea#<+;Eo?64RRqp=#AsGnI=Mtc zzARLGOZ8i~n+-UOg0h9qk>MA&QIY)$n>#{Hl_idIn#~i@tH#pbt#rn&Glm1#Cw1_E z^2H>|LI=ZC|DwW@4AI$Zi7S|ho$p2yj49HrZXL|oN;(;TutT0jWTh~%y}J#Go9hT3 zFg=fuMNkC=u_!DO(W}JLzulW43xvkS(HQ;Y3_EPZ!9gJuF95o{{$MnwXcEdibN+Ee zpNlABV@h*PTURI!=7ay2MBAs>!L;2<0;z|Pi-T$13Kp3dPhoXXl2=_Y2eO{ZyHP&0 zPU{-vQL>|c2$>L`2xDn-{6U{G2ZB!uPhtvDi%X0Np2VyA?!H1X=jl=Gr&z*Pct|bKNIhr z*R{D?;SSJC2mrl>Y3lv5Bs*k$B^GSTU1BPr5{+`a%Gypz8lE!lKOZ&A7Ux#nA(1kK z#*ur=`ZD~ck;79Gl{r}~fKojs-v??Gpt`t4Ub5hID66rvikf`sJZiNCAh zE{ER+vfE|aMLZ?XeZeOvWo0Cx$ORCYV|RGM2V?v17AOO3gF!Q{SPFJAa!<>~U)(Q> zmhm9Cb#rP2YOL(tsq7-DuXWQ`MysR<{hTRyc7t@RIUc-KZ-~0b%H+#Xzn2z^Zmw7V zs$^hJAt9nFiO%tO#hF6c^GB zBRb365AsbJS5y8}O|>%w8sZhce^;H&WX>D$B)O@^S)i*3)*u^r7)34?bg49Fw#e}3 zw-LE`-CJ&lqDRW-ICsQ^Q1oX;c!kdn4DZs$DJwW!-e<`EZOWq;AA0475HRP9FwUmL zCpoABcZ_#r9``Vu8RLCnP1&f{sO1I^y4edce+Pty`q;JERXAvUq+j~7HAHnO4(0%> zr>PW-pBVr(hrFPG#zzR(%x&6zj+?n(P&&HFWPfH%6y=x=w7;dS_fr}#ow-j6jFBy= zsOl^6%(k36j+^?07Nn~PHKAHt5wMEn&%lzUQ&DZ<{EQQQV0{%E&OG!2W0**=#DFdG zlC&m8hujr?X2A zG;*-or-IjiJVrL$>W|a#DDbvptAwLtd|b28JMv4}#n5;SeR;_~9W4#8<&vnZx9tspZs{U17{k4VO%FVdF+`HQ;)<_&&H6C)&@#doD>s}u6v9ablksdkp zt8ulpxLVQ^YE2Xs&e=paZLqQd(v zdLMN~r2yV-su>VCEP^Ev9N>Or_-#dwcm8huqgT|KZH1jDQcss99+pmMd1uh4&VlXA z!J@%}O$819m+D|i@sY2r4vGzE*e}sATC?9q(1}r?LLQ4QMB7tu2uhlu&5p27eK<7fO716H9MX z*Zbu<*z=6|bS@Vapl*qIL$5&|QXmEV_d#xjlt5y_^1Sk`ebTvM~o3 zn|ktEPbWz)af=+xyM+4oHg4J+%yj&*J=@%J@eKG~lQ;tpRsNs~wCiDg%H0$t#J5|@ zN=KLIIJH={Mou0)oBEWaWUQ+1F*;e?B{t>31{H^69Tk`9Atg^rMKV?+PHvRqSG>;- z83o{mnzWiVCqK3Ngp!j2#of`QOeLLfM_8SeJM5#Bj=1Uk>*-G;4p=EoCx5&Djx z6Hz?Z7k3JUUyu|=a25T12mbX4VG`czrN11oh1Jh$4YU^ddi<;_+Fm@WarX(>{pToT zv3n7+h~jSP?8qWR?MUX4{BQ*_{y8an;5+GhJNpJP1AKGfpD=z1e+Jg=>Zcn;@J}T5 znLH2dg{Z#*VvB2D`Pir^Mjw$Y9z^Hka%4UE_^SmcG^f?%?#(F|VlQMo)U|2H`EF*w z_GJhabR+>7>8nNem#kK;5RssJHS<6^1IVv}x;_)lZ!Z*Mo!u%|Q`GL%on17RekcBf z@f&wB+(|`yKPemOy;N*};IUId_rjinNf6ii-nNB&F!me{Gfrd<_8v)Kc+MJxQ=5Fs z4wL=GjEC)*@h&b&GO>k121HeHYQuvlAS{Q#4MysZ8A}%NVhTy?bd7rQ;$1i_Z$RM6%9 zQ9(>{JE&meV7ae<-9-o}8s905a5_42u@R3l@Tm6-|NVJ$b+mHk{z0r+WecCtPaO?X zD?XIRgzHgah2P882P_gnswW=?ExztOevSLNM6I5ty8)nb=WCk=(d%y9L5E@Spr;Mg z*{xzuh4$MC2uk1t)yC9;#n!uSJe;`_dNDB5{ZkH za9R&G#4wvM8~s!{=>z_cJ^x4p$(#cIi!VF-o)hV0D6Fn!;V8MMnumv-+#KPyXP36X zYp4*XhVhOUhxg_g)TdnTzINyN%k(rXL=Q9gQ7V`Q^6Xl*SSm5=)0m5`TQHG4OEHLB zY&5wwf|WJq=w;k`Pm8PMo?z;FtV1ok?AQ{YA}c39I>jd_HkGz46RQsBG(C1(!xw_G}w)barg7#xiHbyVPDZa~r#_=s0gHUB~pmu|3~YcnEF8 z@wz?@?2k$hD=e%FBCabM^&_O&lu#r zEEz<=h{$QZm0mS75bRW+i6djb9sWGoejBW8byO4|(Dy8?)D0TN2IM91dq9?g%#WYZ z6nJ_h>N&IdYUK2x1PGOs(|YH%NYK;RiV!NQ35+uFd;!_s6L5L=Flxl^#2Wl-wYc!* zWH+u=#MeD1u<9xB1gAL|AabQ%Tq3^|d8Rsn(MXN`Si1tn24|?$|1EYqNI0lqpvtGX z6vDL@S+S76XucYe!S;+0EHV1n;1nG!5y!^w8q|{;dO4)4-k_un8K|Uh3d?MN)h$&n zv_BEWCQ$)oj$rz6(BuNzErMinzZy z_)v+b4H0oUeDph$dIN#QZ15+~MWQOQ{e(Cn$1JE3|G-L3$cthdddTFMi=BvV%HeAq z@^9AQWoHrbcgySRc>Y$Ow2FjkstYRI0!g~QD7V#Y2)?l4D~uVTMy$^Si$?`6ER;oOVGPwniamB)ZS)3%#IGvxPIn?Fh-B7;+rJh^!Txqr^>D@hA}; zfW(21Gxs~9tdELh048Hfy`bW-&MwH=-q%ICN)CG=K5zT&`TAvUFkP1segUTwlf1-J z4v$;J2W|aCys8RfqL1-J+O}sPX9&wXg-bgQ4%S_@@>zS0pXe2f250ohgc1rp**Z%r z=>)aWhp}>vUQA?o7=6+ruZcUC6^t@zGQ6tFZQhR*gkZ17Sw6;`n9H2}Dqy4v zqp*35hvm4a1kuVrbCB%N%EP}?Aq6P%d$H_(Z)95d30n{qX*t9;R*%tGr|Zz#uAt(d1BT@|3lnBGy#*@hWwV)e3G>Y!g^v6?lIPqnEHUPlHS? z$1bxoeP-oIwN5ipXNz?}$NUCP#ngTvm5TLr{7}R@ua!hfD-iF)^x3z4ie1A&%ud+fAXP*3;G$z`4-z8jSf4qrFt|vl1F5Gmkk5R4Y~rMqRgY_!!cfai!a>bKJ(B@VT&8w?NaNfJPd4` z$Dtxo8sA4pu&-^Ne}8LkLGr~qL^7%m<2e+)R76#uX#^U`&0xTx<^pla4)yw-po6L>Lxf9}$ag z$jXZE)o*tFuqUI`LteusJEq2}$ohs2ox%v>*3e-aNMvYcP%p{dmA2+!`clLpDn3?t z#ahYyD*bs$rad1i8xzTo_t@bVY(J*#`KDQ40auH6fNxG!Y}|nrxaL{q0A%&2@w5_! z<--SzeUOZyd6zDgm9CNAvEo#k9Wx7rW78pTTq!(=scSiTdt+Aflp9=g_@ts-Sp{OD zIbXWUDo5vJ5(tf&5)G;~wymd$SC$MioxVRw60?VdSPX2I&WZ)S{A+V$-Pdcs-7vOk z0z(~UwVFuO&BWW;hy>NT@i+giOHYb`D0w;m+v=w4J2!qm6nzFW{$WEexWn0$Sa`PE z3AE8&S2GrIV^;GY$S`WhOb8WdW{Zd6m!Sr4bOUj;=9%QgI4hgOd~CR*6P}jAp7L_N zUhzsiH_MAA$ZGN0h3aXA=T?PG;Ddnlq{z@IBUmm@y zF8{z*4ov$!h39j^>`xWyex&?^U1>s~0$HK1^?di3;I!gLMHA1I|-P$`%s?VuV z?H=1}v$t4}tb>&$CNp-jLE{6JK*^||ziAL+A)-VdK38<66(;J?DX%+chbghM^ z-L>SgsLFVu!{T~4gp>_zps`;Jx0T%{ArT({XPbudL+Fp{W(N3yLyZeOv)OgeO?Hwg z<_xJ9<3o#J11yfXIUR{g&TYPWn9Yoj5g%}Ph&4UQEJWGUDM&SceU?)i%E*44Olnh6 zYN`S;BWeep^JMTqW+Q2cpO@WK>4XW~&N_Fw7han)(B?$^M^%BAE>9Dbw5?N2^P>2%u8+yu?yvXYV(n`~ia%2|*`>ds*tfz<@EG8h#b52gZSWXy zi7Ls((*i2M9KSkG&Lcw&r`5m4kPCMT6i=sSk+Bs-J}Luk1~T3?Eb@;fG*#Cs;WB;9 z$hFd$RXUN>PoW^2ko?^3WyIIs7)MDjg;Z|!uv#fJ;V2-)6inT5&6`5=?t9E?v6b!W zL~1e5U~Jubnga>2rg~izXSRL1U#$(!*bKeTAh@HV%MT%DE~e8bZ>(h=%MEKR5>BOU zVZ~1?qz({41%9;0)8?0#ZjPv?<9Y}a>mHGxN)Y3Q&>K9oBvK>LhKYBNs7+}-|K2%h zFEku5xLErXM}I_VD!~_wEy-#X&akVe2?;tiY-A+r#U0!c1#a-%fNb)BExUDJYw9a^ zYY%QoL0&bT!g~G1USa@ZDxrY7P~=&DX*g6IF0P_iU+k=+7c)YOxMeODVNVSs#(VR{ zS|I#hZu}$&L0CmFvfZ-zrl}wtI>82bq6Byn=-*&@=RGhpdK19LxP(E0)lGjUpno@J zB&!V7EgmO~#;sGb`z%px62lFXIAZxgH(Zy5tB)AB%0ZTBCSXQ`T%Y93sJR8~EGWqH z7I1WTgB^e-QlJT7NYWrxj~)sm9{D0^5U#cZq&u(ncwtVXKrjXO;$ksZtw=l~7qR|( z=von6m1+BQy9nHpA}xoU86tjiN;**&!6J^GZ~Ud z8bcD7#z%rmLh0qsGtjT+dhQSu+5D+VrO=1;`wO8|K0amq@%)4qA+RO(=pNH=7l)#d zZY##vWRZzcSvD~4rG6%>hFXskOr={-_dEf1u#SGuXYC;}QG5G0^v>*4yu$Lj>JpMI zv7vhTbS?8R9yk^I^P;jDVn0*KAdvu$oYr-gA$y_tFngi<=U*2I@$c39wH1b_Kt8hR zJ{vELf<{&A)@E)60sm)AthN-U9KBSJOOjAi04zqxj|w3ux5Q?3M#-`^bTkR?x$?9< zLWpgNr9oMWZ0{U441=>xB6CYl9P-Jd_DT*xfkBz72tBKFULkHceqn;2u=tp)K>-d^ zyb#I^aFyh(0#UX@J`d#8vAs|(D1PRCA_G!X80?l-H;dRu*K^aPt{iJhAuSet5)hhYG~&gu)a_dS%!7cXxi9kj*;eELRPlw*{C(Io`!zn}gT;MO;-5CT> zKXG?OQGv4#jJ97yZJ!HA1EkWjBoA8rl4ovxU!WKGx`kAt1QxZGOQPK{3U!EC28c|Q zq*vezNyhK^`RGarxfqZMJDD8DQFM)&Y-%A3>DuSp1UX z6e46(QI1i8?3ElFS?VE(B3_<7aVWs3j3oHeDP)l~X0>up1CMct1OWcvX+Y?f2D)Py zc12w=nT}Ku6irjrJ-z%@qHGZ;msjQxaodg4Qw79_ydTWswm2i#9!qYv?ZENK(HXfd z=%fa`cXPa@k~)wEyl)fZ%l2;d=B@cmU$)FulW^I^)5--k%K$3i^_$IYp&Rhf(i{f* zf($4KZK>$@mxB%zK0I>dUWV)Dbtz>)yOepm9@RtX^}%Ja@ks~#@yWRs+$z;k=@-Iy zt$QPG94YL(UNP`W$0fHeObHj_gq`%zkC5CEoZqF{LdBSxh}v4re8?vJnmR=O)vxV^-^( zcOpSQzL601Ish=_-ZJU_)11QHJh4#rp@NYZ};p6JAe@Pa;akm6dy0TzQ}1IZ!8 z{yoMD0yprt#K3?3T_raOYA*)+!Vjl5Yj>2_h~xGUI2QlCE{+3=A5_J^49|gfDdow= z$Mf?pW|u-!;-nS6CqU9aDD}QER9a4eryGaSX6wbdFqz>ZFmUME5lWc(ysur>cAN1J zSsJfCgyNwR(mtW2xR82A4CV3t%E>{6r|y{-==$_h-=84Hz0L`fz z_koWy)fS9j>jHiiY5mvepcv{1j_I<{NJ-3T&8mCgaHJ#U^07u!*r?>z-DEj72bKA# znRr7C@Wd?GkUEF~y0!lWqTxG?Qdt@HCs6o(;s7=;E&sT!r%YX;fV1xcUi;~e8VNad z0}nCRX4)?Vi$r<-Er)A0U+jG#JXR5nDzdK55iqiSK0WB?3@)v5zcB<}o~iEoJW>R> zVnI!eOMS-tmgycUWgH`Vak29?8-2$;>`HwE6y7<#HXG{<|uxKxMh;{$U@Vx zH(w;`@4#I|r^=Pqqy49|6s{CuxU16A1ZG{@N>r6!BONLoWm4r{tC9F8Wn>#yiim`( z$X}3LLLhh336_`)S5$6)wUB?jk!PmdiUrZ*Am?O>v-INlt@u23rHNweHrZjKD@K+E zj|GRXt_oO*A$EH~E##l~;4n958XV+O2sMZ!Q3%H&o081Yft+%t(m`+;bOs!eaW0Y# zMW?Y=^}nkZv}Bv_;)$>sS2wW9(kk+;7Q=nQ|6Vdgo&<|VU3DWa?etZ6AK+Et3ZXoH zg^EYnIPa#Xu>GFY>#;8JR(|r9Jj&wxfg5MzD$Mt@WwUs z8K;&m^q=)wsL#l(zfbqZ8MMfw0RpF3_Vxz77Ts?LQtT*g1PmJV&tHS7 z#b?VV$Dla;BOWfU#oXE=Z|6%E>1DbljJ|HoX7%-4#qoli0WQ-g=TBJreR6x2!i8;wBjR;7G z5`su0ElN2;MWv)9CWrzO;`e><_x^X;z4v*Zd(S=RIp=de=O7JM5yYI>$#^861O|UqcX8vnni5Kzslx=K}M=bw&3gWMznhT>)a0| z$0f;A9*4MUZ+1bpUpVT1fgxZc$Cr9S>A0#4b8-xX;C~kj=t%9NsIR(D!^9}@mQHi-9?=sgb5iE{?Jg3)C zC*BwXjV9`GB?4(jJlB~ym&ME-a&+*X)E}^HOJNkDh<{iRih|AvI0uD5DylYFf=l6z zt01lq^h!hY*L$GWY+<3Hlf)In`?1|l?huyu!RmT+MV~bLu1C3?ciS-3^M$V-16{;F zAh@z|MB2f=f*5DT5Xo66yzhKh3{oLaoe+N0PEfT}sPmVF0lm)n;Y~=c4Y^Z72S3T# z8&=GWA}7Q8nP$yV@f(K<_^_4bOYHZaUTt>VFhq(;C3b%7b7{H3P!#v09f5-nQ|d-a zb5OIPy}h=T1oK{Brz{>K=d()-FaF`HAJ4E*cumfn+1BlwIsU5t6(>{U*y0> z`|9|+zf=fe;}7T=zd4%z@0%#?8Ah;ests3AH0eu3)9mNkQcA^FuuypmDoo4-IC`T+ z)}`2~r#NHV*e=*DFLol<-U$%AY=u>=EcqFczT1pNc}L3v@G%1OUbB(N`fWDd_mE=m zLmZ?}%A8=K#bIZc%M8$C;vGf2oQZr$S@eM$L{UyQ7+IVKqki@-8!`1jcXk%f74->9 zk8w5{pME^*9FaUa$Cb`G>fDCzD4v6tAHJF$qI`Rq;j6VX*-2gHjuLfDqbl`)$bJC7 zs}^Uh=xr71)Wbzn>Zf}+h{Az&Grz=JD#TTlCydC$F0S4Z{B-0c9>U;j5<0RJJ{3DK z=cUcRoQ<&FN<);guC~o%jOcqAx*buK(meNq=_4>X8=xzvLJ;`kDXxt?i?;u9shQ`jtRK~) z-Cr5_oo@m)I#*AxKFGh@#DkP}p;>_VvZVI0>*<+|R(asLj+$nZugW7nHH)EZ^YrGq zbi#M$pu5HKt*iwAGXA+W4vjdg_l_@-y#AIHJ)IjONh@-RM<;PF36W!?JGB|3h`qt^ zmHPPQEWMIZv1EGU4Hd%lVP zG~A=;wat*;eBSy>N@wXDJpcIBY~nc}P`Ai15D$1SnLztLaBi$Mexm>T2D^h zek4l})$nQTDdfFZUwX}7UgU}0IU4vkG-pVlf)dGF2$HTkFqc(cb-HohG<9^paphjEu6Wh+ zhhid`*I5E@PzOE#jKg0}OyhQ7ewK2-#sHk-viBKs>NiNIo98brRq(|6no^SP{hadE z9Ry*CO6@gDzQi22(G(v<;2!b@tD2Qz}-gh~$ZA**Z}( zJpY`L!>(fF>)8|^pZ7Z7w3R2qCvPNnITvkx9f0KUb`S%0rNFJF28J`KmY7jzhSOrb zj(h;Y<@%R7l6Kdq?G?1fl*@&ez;s?rtl*8xAreR#;R;IQekD0+1WzO6r4tnFP#CG+}FiO7n!LknYi z9f6HcB$KXDxJ^8S2X*E|g@)UXTZHE2?iUD^UROux9LY}zl7G8(t7R&UCU^V>Zhw*2 zTRu`gHjpLKQK?Eclt7y$LNmJi1r~3l@qy+dq)DOMZM~sRA?^wWdWC;rj$>tKnibrL|=>qDpe#2&;PKAj#briaJh32`gw8J8Dq1n<}Z{)c*-zfBD=^~{? z)>M21r|-qIOV^EP-gV!y+7jH41ZK8SBvY16@g{YU+>DsMsMmC3VrtCSa*9hsY3QVw zulnBd@_qW<6n!Z=ZdgJ}mp1M#RliozZ`)Q`xfvLI|EUwTiQG(v97hP6!?p0Mmt>co z_0>niJ}Ln-i#{R}KtO6Gr(jl_2Zi+y)L;ki^U;3Nb0PqDp3TPn^Ht9=ehO(K3ZPe2c#cS`iV2wyUo%X z;uS*%$la`(9zNIz$!Dq>OUgN1^zks5Oo6uyn+N|Y%Cc^td+lCudQmdwp5Y7msfi#V zT$w|IHfQDI%Zx?dzlTY>lPOI$$&MqzgJoGT>T8YDU*;N>W+6IfN`T=sdpX0kVV@0C z8V|B6q&<}WK=Wfu<^jR0^po`?q_j9rJ2wS>aZ&ai;oDH_X zX2a5W14z^OmI*96lJe43Rhsx4-->1pGr~FKie((IuD%OE3MR=fSmb(wOK#_IUR#od zUTL84_luOJkWc`*&A5Rw3b~pnde;}FI?iitmZIk(P6FB=Eo0UpGy#r}(DLgo93Gz` z#g~u9pc(Hwiyn>fls#*dGNI>Q>v+@zTbi>l_?9wh^YDh%-2zy8Y$4v|a}6X@_~{ny zE_(q(O3<9x2amzt(n8`G4{|M$-P`+`8E|SB0;+DX4wSN)KfK|NEB*DlUi8gNK$;2{ z!g;v`D7G&@Mlh(?8E-J=t1^ipYfYWv#YHnO#QtrkLtV;OjV)9hm}CFFlU|g1&qyS0 zh7`VZk!ATaXYN|ZI7ghgenp6+l5iJyZ(z)HJ4Ol+R12&?_JZRMAbOk%CtT5pL+~R2 z2fwB4+vHpfm<oK);d`56=&<4`FJ$^fGAs4d`4e-8(%fP z{m?s85j^jN9QL{CChvVQQFg`X-cq_{1Nkb%cll|3zYw}_qf%A;1Es#q*aqD<{j3Yl z-4x$UjS>^eM&hEG#&|r33*{WB+_=^JU3!x$`8F1Cs&#CBTtIP-~G^@(QhwM!$ zD%j$=kOco)?t9rfJ@(;mq5Ypw^)9a1xWBU`-QS~9PH!fbJBrS|m^N1R{|k7LVBSYE zty`_&V!1mzA4n#r*Tmz25t&t`i50=tOW-2g{HTlG;5?%%a#zNoi515ZN|mBCZc5*J zz=)|60@sH!;&)xUQ(RC+xC2kZRh<8#T^0W&T+{5qE(I)@Z9rbp9m#yF@%!{(jD2$$)%fG`G2l~=7Wpg&5;S0T? zkqg11pDmKdLg|~YB8?u|M|=GC4akib+14LeJ1b+0KMhNWfrt~spzaO0J3WB6Gu=KB z)}jgq-d8EaX$3jH6+Q%H9m2G+P=Hp52YUMu4{S%~#T0IH{AdBJ>pxm^$(c zPlzd?_#6Nt5MsX;(&qsg{M72(2G(rR+@VZOgV=8}asA^9o{8+k)#~m*ktoAK%UdtA z-!$?*9Nbi2hdvW90eC_GR`F#*@;~gqYrT^+z@vpJpAOqpxc1X)t)hA7Szfh+s?%Gi zga^14jl|CUpYqsg({H5vViz)vph@XeSj%lQ?P|)|^MxrdV}OlRT#EB!4rrjL&ucvo z-E=jlS#ytC24x?rm(RP#zq11T0>p1IcowBOjdtWHaA!5uj?dxqtV2u~+Qqwh&lL(# zVe`p?@DJu%niYNh8>KACH(1!~Dn7hvm&Yd-oqbDQ`!ZxR^ma7o{CI$8(C>W2lsO1T z8bkwXr^^Hwiq_4koxHtfczVZo(-Bi#%zJcbb#aKiEFgB*z}9h)N7KcdHj2G?cgP3w z3j{ANsnKpUp2UM%JAd$FB942vtQMV-9!r8s?5nJ;zk5pp=a7uVp7W^ZM|0dd?C*@P zCwYa+K7Dg(%-L3+U1|PfSxx|kMWoUj2(U*KaC&l;2As$0$IJgOZXU&;X#;rsRg zbPWGs&I3vr^z@i{r8LkG8sQGfB&nhlb^J8$r*z}p<5SXOZ>8mdtyAWYCxpFkIiEdU zAaFKF{6Z)z0n#atPgMn=z;w$>JHq{atUN0eIocXL6N*Q5b8fL&pU) zXuBtY2U2?p;!Q7X7pKm95FmZHS>HU6`t|2^obFnR$2&%<)H#Mdo+4Ez#?E!Lg7KfT z8JoA*f)h#cRjO2r#eZw8C@7z%^S21Blo|{esDFwtu{7g=*A_9qy7cBPm2T7aIf}^r z^ytbv%v6hMdsitKcEi_IVy_Wtx~bo!M~i6#ju!hOSr!VugB_!Lb!aT*uWe9I}yGq%RDHOQ)X(8dAQ?S^rfn0j-ab|wpD&PZ!sW$#6Ol)n0prrrUc09$>i=hi3yPuc3K$R@i_ zKNrQl+mtvu`W5ArRCR&xPV6oZGQMN@0fF?cWxI*htYNx6VC1$OwPV`!mu{^bK+$Ob z1#rHH6>XH3=JdT&hV|KmXmuuJq1V1`u zM>M1==pLmY#QMM<+H+yQ);?*QAw|lZ0}^M!AsI)RM(%+gaS7C1kfV)qDWl!3bXDDl=YU& z7SV{m)Z@N4n>YqL}qB@@`uyTC=g0|f-y4POFvkkBkJNZD7-z#&_n*!KxL$0ssfaD(# zEJWB0>`dg$38e9`$}WoyBV?sg;xozO9gs<(Cn7E?$Jg79zd0&}bC&jhUZr&^+9ni+ zUGCT~T#Tw3X5A|-Tcq7}!aM`w zw;tKal3+`NdI#1$_boN(Vuz_eIXwgg>81$uQmoSV%iR zqiMA{M||Lf3uBR_HTnB3LPH1FhAQS(TW;*6_!p54qk&A_OY7Asl^60^S-_*vTRfRM zI9VH5SF+M7Ux37)8wXHd_Me3>aRpM^%M-6C!n7k;p)|jWtaMCw7a^P$Po(Ml zV32S1i-;Ag`RsaALuTy6TRBJ{OFwR16UAv5KJW9oE51N-sY8IlO(IpSP2uJ>KL~Bc zh5wi=Yc!{xh|!eAU4;_dA!)yHkf)aTS^vhhXGIA#=#|>;@)Ive#b-eo_oIpXh~)Z? zso;dz0Lf61C@vj;Q?ifTuJ|?7b3EAlh!#I<5cV=1l@se^Pxe5apZN%}g5x$$D+>+a zkErFlV_XO2@e#$-!}iO6JaDctm~jy?f)-CN?rpZOx}e6f)c)ROlz)?ve(y8KVBqt@(I0o zKoI2+JBL3U9DasqSs0ij2FlL^+v}$0#MrkE+hL{gG=e8=H(4#}GxUM_@@>b8j59DX zW&D4l^g}7Q$Dm66*`K2Jc=keoEKrxR-o$d6Q9l2D`E0y7^?hHV%2kK%cq-3?{DjLS zP)%8*#_pPEK2k@uXmu)C>J|Nsk@iz;XzkfRPzMle&D{`UCh{HzLcFW@nlJH_c)2fm8b`qWO3)=zMd+FPkXo(p5?h?WZG zy$fihIK(MMcGfn}nAOSddddZqMN09_xYf@=D%8I5O^iAc6YT)BDfvS0Wt80}V`)K8 zrP@EoE&zO~=dk+b9;A^oLrS%UuYPry<#)@EyA6r*f`?lKUWl(P6Y9muZT0ae+yki3%eE z1X~TUOw0u7;R{w*L8JpbA|3bx{{{S$sT=Qd&FR=wh=)FhzPMsUMYfX1Jq*bZWhUz# zZwL$sqdF_>mihLx4~2ywF|J?V+&9w(z=i86z|K+{8K8tDGiZwE*8xlRLH`_+bNb4t zMHfrLrTPm5xh!en|$2Ip?ACyMQoI@3d>oLQM9ds(ro+`>1I%`f1s z=!bI-lC;f>8Y8=--4XW#!Yy6t*R?Pn z7seDRONls719M`!BQ#m4vbE@f0rbd1HrTDWIOyWJSbv7m-DNQx&HaYfFC(rjkvtdU zmjMU|R&WI=M=Xjd;FWG&Q|_pHCeL0rX$?&V8D%hl25exCAdfUBcx8#P-xh!0n@7w&EMeA)f0yd{=v%jng4uT#NVS9E2k)RT6<6w)O+@ldZK)c>>|Jy%!w*? zCdiTC`LbduQheO^e-qQ>lIlQEs%<_7_j$uwR#;Gwhx`ofQTz9xi10|40b2Glco2PJ zCm(S->l!L&RBS&4a_mSX=x2%1d81BWhnUEIh}xe0sf*MD)b#Z_dd8S6B>r(HJOn)* z-=mh#CQ@8%%|V6E;C|0+PBfS_(5nL7!~^uBoGdK?#vqpI#TMsJ&G2J6<7V!3ZUB_j zmtI2R&Ja5X4y>Z5&HN?0r+QA0wt|6F^&Fz7{f_3;qiM@|5%osvg4COP6)Yi^8g7JI zUL*w1f(ZRitmPei1Lv=p$V|joJP6}O%6XM(qAo200R*jxLCf7!FeDSZF)#SY97)%X zcqe|1>C)7^7<-wO3-kei4I~W?rPpp=&ejIXT@8wZwiaSFSgsB&8cKX$6L$sbp6IMezhB?vE9Rng6v zbr~5<=}1Fj;j1!v15mpL0C{TdBR3p274u1!F-1L=Bp{Jn+uwVUOXuF6#of}3jMzd7 zy^riXd*jfJhGH_>Z|PZ4j2@b&N9TR&60OGSHea{8~I4-XoTu&q}0j6C$_+VcrcB2CeM) zw9AY!#n|nkZqQeoF1^*a11#HO4tg_^FDRI8BkUCEzpgYVqhe0eqR|z5(h?Qbu||0=y85QT9m97R{_vm)FqD4i(Ur zc9u=x+EB1sg@#B_|3h9%#bk9RllU}D$zQjToj*rn;_TsP+{dcxB+)>Y!k%ZJC+t1x zj6VB1kJXj!i77LcC}$wr%rr@`jOwQAh`7H}{i`5Wxt^Soyj;#S00gFC5)|uc|M+^~ zl?}ob>+!+z3ca}{hFl>SdM@u#|7nIa@YWhv<1;EVXKi0$FcLhpwcoc1v{wQ%7~>8f z{ysh8<>`|sD~$QTOVbUHj_|`3DqmCNS^XzZbU2dn$AE?T4dNNshZ(?3DjQhJs8(a> zH^AUS=fL25jjOoBRwTyE>5-T?-%4yLFY?FgHxHaXh6rbxa9|j%#z2p{Q$MLHgp5eW;> z%kMA6r%iB47!I-E(R!fvLr}aT(1N~39aLe0_cuYx6@t9PfuO0yQ~`g*p_WHhqzvBD zG%%O>3HZHkTBXzN3O%c1q;3h%m^G^=H5{$*3Z$58WthvFV=i=}Bf6zz1DYwm=*zhtOgj_;=)sAk`he6TtR$rZo%JVBbPSIf_=;i-o$hHu8U zSI3&W*CpYxWUE3b;fLMgHzsRCkdUnFA7>vPDM9?n3~OW6J@J6t4X*6M#G_FMk~_hk zw?JXmUv!?6zZHO0T@sol8Hs-2X6zPvV5blIcLS3x`tQ>EP!W;{S8puvH*;J#z`b=( zO?-CRhH|deH&dzt|Mt)OD5nPq2}UsA9LLO?2d9I`{<8a-4kn&%$KC+}OBZV-S3}zA znTocv$bXCNl=K`>1*6qBi>PWb)IVyNPc(k}!nE&N7|;~3+8da=;l={ay24l^*8|4R zqi~wgNXK3MZ?a$lVZS;?OyI_tv86*HUjWvZ5fN%m{+`QmI+~2keu#A5;s;vD6Z>i= zjph57IeXuplql0E;P-8OEvz|k-P9v-h+U5O&e>@Mm@8Qt>vvzAZ}=@V_x!+|@7BXy zcgVL%tl!;9pVHM`W#tOlRN0*iB*_`!xrFGJH?#Zb%6N|axli<#*4KCMHm-pob3-!E3KXIY!a!ASCeT;r%)|P5v^Rhb zlC)<*K#DEHgiETu0Xi#+sc+T`4FcJ{zKtqh9a!d=BQl5xZ<6fb(#Zy?G3?J99FY$8 zBj-`mod8NS7;zzxEV4T~7jLV4%+D@v(9UUTo#ECgDV+Y&|&m}5GT03xQYo{r4C zA3r8fEiSRYfB75yKM9E&L1r#gZi{_Zb>#Gey5DBI@x_=Jw(&9{SM9QiDoP;c%BYdC zb>;p`Z9&xiKR?86GZzE6ccxLc8`?Do@S{J0V%?m>YVWALtR;+VO|PPeodG`B5Ms3b zYS`X+_D%M0N9;N_8m~lOf8$Iji)Kcc-Y)OrOz2uQM@Z*Nq_|S0aJI%F6&__wt6|DK zlPL0s?4P1R3jhd7o;p)7VoP85b$p?wQd#LK!$^Xi>x|JK9K>Ho@Ot2jIP*bLG)4ZA zV%bxelAyK$G=O%pem*hTNKpFzUBKmJGtQ*Hi#)qO3QerS$}lPbABjFMyG_N?M&l0+ zr3L}c2Q)cIHyVHfyZv*tOTQXGZ^8;d^ym$G(%C2&C>`k!ME9!hoPteR!<8%4Yn-_Y zlPpt$3SNGYUK6rUJFyIn%rO(*Z{mFO5KstGQw>m%Uaj{|@lP>eVSLEErN3S{?Py{l zS6~sa&Q+Lpet!O66$qsy5Rb6>WqaUsqRnO_sw8MeAtV;O`~8*22=KU_d{iPF_01@T zPgsA_?;q=s-jS@89%#^ZiQWJcrG|~4g$)PpI<(LoLT^j{?gdK#$mERV{(cTuC-`i^ z*q6M10|J>CI68U965RE4h`Vqy+dk(YaFacSb>3>*{z0WgrVUGq291UnxzMWgfE zR|A_Wqpk5upvO0X9#;ZAe&FXqp|(*FF)-Ip%}43~AY6u`JOeZ>H__st7@WC4Y1!I& zl$3aU(t78fMJD~zgw3JIE_SzH2UG^$7(! zav9sK!?{b>AjLw*V`usLCE7K*m2-vV=mtfw?;$8*IfP!It&S zeoz-+S?Iy+|NV*n@$6}}xJJ}lLc%*_XM@?|7j~%RgXYdNlEqK0r>L%H(w}Wfc#1*o zxF?&{`ERLO+W6uZFCP3YlrZi$y}kPBT>R~D2?~Y|I!b@zFc}JNEJ(fWZP-)vr{x`` z#6Px+5>Vag`FpiXrxK0Wxp8+p-#w7crp)~&HiHPQR(+yBZCjjq_3IOrW~B)P-_Y!njQ%Q4gz%H*EL? z(G`D9swDCy~N3H;Rk4A%Jdh(38_f$+0oq%wM`mJjbLQ?Q-JsBkbT?; zqYqBJ3X`UXtGj@m1|P+^l$_^fo>-r&WY0nuE$-O8zyCzMQdh{hKjWwO!lS;~efk{$ zRh;?mRaZCKXxzQ$%=fJhjvgeL)Dd-TN#)s_AB=5USsMgx9N@3_Z!1zuHUnuhJ)hy? zc6cU5nx1$j1VJhTD9OPHeB7*oE_<{#o|e1}3+kNQYwyLOulHp@K}bX}J^rlZ@LQ5@ zomtFuZiI@`@Zj>cj@-_FCG$fKt&kpyx8?{w9?5&VL7QqdwBZ^A+P2_+ zVT=aL0gc zk9H7;RQ$z~ho<=bo7_~6-jcMNiC8Q*hh#IWM_T-_<-$5hTc7@9aqiXBKtjWWy!@L< zmVU2656FcNfH3ySUtX!+zN$Mr%a;$Srnmn&(d!%eJ-gy1kiAJ2mt>!fMQ_FxrTX&e zH7_KG_<=qp2IJCvNtv-=fw0+F8B~3pDFtLe?(>sH$jg<8)OS83b>`+#ktg(XU(aV4^*CbC9#HX1w zI=^~;_CX_Lz$U9`pAAa+e}8|rP*@F!5MZMowD_h+{_bY%aGE#YgvGNj17?PAK9&5h z6HxKvSfPFSH|L3jOHH_79bnc$3^@72+_G8Yc?jF%D29K1<)i8zex{|Bh{cmzIPfWUq z0p1b#4Qn7_dL6pr=~e}oKV#79CG1+F{4r?2^B#Tg-R|zDm>aE-FbH#xf_L5f-@HSe z#hdgGZ+m@J4^fom}EqZ%N1M6LFw-RjCv`g6HM*)N`HSN@u2Fqi=D?1y5bew+Q4Fd zq7Hmo@<8#|XMxA1h`%kTM5-(K!!h0J!jQ7NiK{~nu2uP>GBU^fkd;JufpHg?PX9xY z0mv?Q8AM1|+e##qmAM!|UxOXe=_7b}#(ml%jo|IFYU>$#4X7U@7zBpH2mfg=O1P6x zHNJl{SZzS0EQIb$Oe?-*gTR#kUssyUxq?)mataDqhgoP3(ufa%OdnG@Uc(Taz}n#`f{)*?3p=v11++qBYf zb?u6OdQ7jg1f=XDAn@;?|me>++#7)`Ah{s^i{VgoO!@gT8N zQWW4qW?W!fo&}ox;!2eTU@7K%8*4`R48T)vfAc&)_wcw)(8^Zgkfvlz0038Hu{oNW zW2~R%7H%5!9=cM+kWC=)4Yr@ong060+zry7@TXs(o@KFD3aQ?m*b?B-LW(bFaOt$W|I=l-k_^~!Z_*`AM@q{G+7FTp`^;iiv)%*b zgi%wEc1GLC5pip7v;aKQsSaD_H!YZs`% zQX<3FuDSfIYiOX|+!Zkp)tcC)H;HjGP>0JqRM@r9FlZ)vYQMxdoRK5rqVJe|V3F&6 zUXquWQ@wN?S1skQwu8Lu61_u9$fxS>{ZK?1OB zv(ALC8hgh0TI(j$YRD^|M{b`i8%(8qT<{CgV-f@ zbz`Th=v>-$4bb&21ZFXbaH9rOlGe)R`oK%YVu1y{8p@y1VQmv?|TN!JOHK z30HW-9RUbj4J8-3VCn$8zY>yhVe-Y*DpC#~xYyO@eBdKgj@D7l#YaI^&)9;?(%dY> z{sKa6Lx~6HboGm(ge*Rhz91<(iN7m^EIy>z#R&hhuntA)q#sdEX;qRmqlO<@ly)1* zVoNlEz>?^L@pmQ~u8QNzJq@^IIj;j)LRIrh2z83#$Ess!a2XtS2Bg#8QDXf*VqdSd z7sX`pmA$N1vIDh+pDeGOYZwLbYfF)*0raOw!0c@>0l1Mx>187slABs9gxm7|B(AB! zZJiB3PH9b=&u&`fB2>gPz|Z!JDBZLkKst#-98f^GXC_}DVCn;Nn>&i&x>!9AbuMn) zCvm7(g$&ST@KbJEN>F}L`6k0q+SwVNyhp5m_j)YhNIRynW=LDWG-UixODzGuHq^7U z7gx)BQBiAch{{#{Hm6EM06(BI&7Y^}Dgeb71I#qq%qzd1vn2cy1pocR@`+4&_LJoP z?7Qr-eOrMitR=Ndtn)qXQ0o_3$^D_XSg0i5e|ygE^UITZLqG4TzwhHLa=MbdLqZ^xFh_}E>X2EH_ppl{#!lkL;HoCqxC#!mT#lAy~F=z_4smyXEz0}^^AFQ?lV zvs#t$dY_FY&1*Va8h!$ZFMja2O&DbdXi5O5sD1>9F@V`L?7A+sU(0a)A|OsdBQ>Q8 zKwk~;^B1?nk%~5E<^yT98#s>0M=NQvr++ygIseQ?>>2?zwH+7vIC{Qq z0&4aKh~MGCJj1^QbtYl`-t`wmf<<#cq-3@J%@J8$Jp4+6>E8nag|!I^S2<-j?N`H+ zIxoElHTyRa|FgA#|NVU0xM*XD`?;c+ndk<3uw2(^UFl(^B%eP^a9IEkwiB?`blQ28 z+%D6SA@S^`0)X7Kw*x>dCjeq)w8>IWGZ_|jPEVmDh;?GEA+svd|Oh2-GvSliD zl9$}CvG@#X!g}eWEK3^(GzaWBhkUnr|DntWjI91*`gp}d{_mOkqAelRjFq&xS(Ty} z*aCSU)>{E}6^CR>jR(@v*{~uX^9Tpc^Yo4grZytaIRTubtswrh(j|ouYPTOZA{6i{ z{iu^7jGe?SS{vZIsM(+Qt~1zrGcAvQ;XU5OZ;>aQeqN``RB*4zx=T^w@X)I?QYock#9sBPj<4veYP z^HK<>^BzRLU;TywP&eyfc*OAGBowm_^b5gyRddcQ^v!hDW7*&`MaPHrCHY)Fw4i{^aE&7} zGH)KVZgAb?h^!ktzV#iVQx+C3hyS%`FihWw*m|~DSStmkt&{;`-c12^`3lfj{#Kvi zaVU8M{H9e4{H|+_eSpCSbs_WleojfwFY+QYpfeKMCf^4{>hKeANi#-AafsO zHG1XbkWY2CtTrf~5gWpQWJ@SyYzs~k`|P@W10;bwIwy>jpMU4n`CVN3xp9pm>SrBG z+AoW{l~b9-66DC(f*SX92t$#8ZMBcu-(T-ZBN1f^*9zD4W(fH;+k^$g`QR@+VO280 z!Ns*OYH{iPTIyk8lo}aSM(JM!yKrEHFC(%q!n@4nb&_Nx2zxW#H-YY2*{L`#T9P{x z4W>hA4f^3;gP>3ZC}sW|z@K?)O8y?QwgQXPOtu*_K>fhvZ)oW{Rq37&@+@F1dSOHU z9udD7ijqrA>P$`?1Az|zdk_TwVvn?Abplm+=+(AE@P$66@vL-2t$k#Rj0_^mg1nvb z(HQAXglCK^_KFmAx>Ia_{|A-qgDiF&-HaVD#}0)>s1YK(ZRQE18?JKtOG!nTI(M!^ zofLFKFlky7Zse}c6MO$EnJ?tK{>YexdkF2lA+RcP#sNqmnE!2#>y;|@xn>b|W+Rhc zGQKGv>AvqiGFJF$XS_)WZY$4!`z|SyCjR|k6ea1}A8U$>Z~3VEX@0j-1m2G*d*NT~ zH)vR0Qh;|&JbadV;1N+fFNiN=yM>(~rnbo5fvjU~4g{$KW3*BVdoYk>E2Gkm4O!vu zoW0YzLnE|CnwgETtfBgtq^p0fa-9IQeMQ?pNaUiURA<%$hv%RuDlzACp;FpDp-_F` zLpkaZkZ%<%E0#1OK31E|4s=-ZQd$T@&uih&D!5`}V43xy{yeKp39Y<~=~@g3d(a-> z0=+TKn?k7*PsnHt2#=bQfgZzegQiEWRTNzIHXTh9KHuMG5+7;GcHkla9ZKO|83t<+ z0rGKEOQ2cD@TpM`D_|IvvX`KzeQHns&YfWma3mJ+5j-^z{yvow--{u|m+~P;5<&V7 zQ$PKMv2rKA^CR6k4k;QWF|l(NKLbD(Ph6;ilAw3R)+<<6;Nyb^(!ddv9Y~g1#{DgaaYP9?N#uWIGOZt2l7b5O(%F!XAD39s~I~j4DA4Q(HQd zlbt8-?KROI0LDnduh(ppqk&?F`6Z_7dT=OQL!+9Pd2VXQ8^)mBTC# z*Z+wbSy(yU&UzhS*QJ1*)PE{D#a_^08W!%eQq5miTkSc^6)W-TpNJ=1Z{0d^g}emW zR=rDrK3OiYuMnpJm`OJV-Vq8LfJRT+{Tgp)Ong+)#$stN@smdHN_{iW!UVv@7GF$% z`6?xv)OpkA6prp0a3*>JI2!(?f3D!s*Va=BKW;rWxalLnxXQZ)g6igA#rkFgp?BD* z=%GpMJ?%(+q;^Naz4@AO$YAZ(WB>qA|U z2R9+kx9dX>K8^i-_Pp9n6P1va@IWUHv_S+omVk&Ko!*rhUAL+rI%_FD2k5=B)gOb; z^O0=Je#OtpW&0)1r%sUezPjJYH2tz0pwm@euo%c5-FQNiTxqX?_$Eqkd||o2g->Yb z?79skmVIo`5wMY1yAFT5Nz#4mGyJn9s{^;uy2o&{9u_N6Y}m@<#-Sp+J|m~-)@|N= zs>6xZ$bhj>l!X>{4V{~!_8DF$b|R><+oMiM=sd!Vd_I*7U|m zxtgRGj15wtDnK+a@#RRFQL#_oZqWvJd;Ob>=o$nx7 zZutOYZ(ymnIhz`=0Jg3-1*Uk-UxQl*{jSPa7nX&N0;~T-(%l4MHK?C!MlJaLm1~^9 zR!wCSKjha;W&iQSl?EG(%NSzc2LCR54#O|=0(q9t{TzjU2HrON?<8P%Z``fda_uf* zr=qox0{-!{;}XQwncp@67C?+25?4Ue>);AQ_Wo6A>~=R9qEysjpoiMq5>;IVdusQJI>jM=4}JP>YQ_(9T)za)2xX5d9hlP zCf*eAwR8{(OgA3HJuj53tIvCl%ZKD4`f5m0SB1{$>Uvc>nA*4_d^(}J_R zfd;?pdK*Ny7(`q|o3a_umNB6QU*Yop2Hh0fL{@%_dh45%9h%&e0&JnQqXqXvD`xa> z?u7jSTAY=WhkG@5-nA_zw1T4kc*lYQUwv^C)vvgel*+QjckgRIQ%(BARoX2+ME(4? zt6lnH#(H!8TdAQ!LNl859SNg#=Micrhf2Mo#$~fXLEB;zw5E}wqP>b~m9398@mtm( zFb?$GENw!NYiEsMogYM4+5k~1(x6qD&p2zS=F@nm5QH&b@J8Ia*ZSKo10@LmhI!@? z=Bv_=C7{pxTookEB>(CT4S|64*}eQrEt5@1bRy4G3fd&n#Kz*37T0aVMX8pKnu zyBv|nk26J$k&3gLiFbPN*FjLr-4;qc3Yo3?BAZ$E(}^3u#hJT4XZh-!s24~okVu`N zp6qRdb{OeO{~Z5hVm2KL+)I=D)p6c#6zM4rnFRi{n<}Xjl;;V zIeh?{YpfCBy6FJ)l56BC(nK-Na-sW8gnt#n*;_(0m|0PwwBOuxJ*$N2GNjnAQ~V~1 z?hTUikxSpc&WSg(G_TOj3uJgEkP>+2?pWZNW`MRkzAd7_5yL**kGDA*ctd?mObb|~ z4`YqJ@W)QbKUn=^9m^0VTx}=&*w=#)HR!JucQ0Kt0)>8Gb3e{sbuN=gUgYCL;&E&-}^vK{UX^qR8Yd0rms*KG-IB8RpQDCs~MOp_+E*X zy`T#ImqE-vUs+dlG1!L-wnVseETunw&mO|C$ci1$a&vpnTlSx$PX{OYzpjjt`g1*F zZWX@-Zrhpb%is&Z&5IvVinoKFX-pEvNL(2#k}k)RqyBf>eZf$W&o~B|2W9v^liDI^ zUx+kG>O52NG^w-iT?yx1n=IwfYhpW!J5^p5Z|KRI^vnsZnxHLKDC}2sZdt=Q*ZH05 zzm-zT(fs@Q0X#dc2Ov)Nbif2H{+hXCk;p^_GJc1+Pc?FetT{B~ZTZ4laE$SfrVjRG zN0?e%vhHsKmh$^Qr50K2pLo{}%?rQFnV^?6yl|2CAT%SqPsJFac%qru!S%312g$)e zdhuhZlPZjZL=mm}=_h>@3UiKIPs4s6YHh-ODK1gV2e|LHS`qDA&DhHI%M3+NBmT*` z!RsMv*ArKKcTir*aE=AfpVz^e>+r(*!-)2O#0D=p<;h{mIQq_Rg&V7{WQMG>=rely z2kB5kCz|vbG2g+iE1#M z_|Ginr~usZ)%$w5GJdFWTJ!mq?J2o= zeVR-CxhpL!&%u9z9qKu@6k02}TS2jA8S&#p&lCI?$HO}_Z^l7B@umhH=+*RO4}m@U znFPJK&70Ey_;QM)w|bop>1{-YJfPc%IcmgH-;_1jv1MYStk&Ej z>$LS#r(`)dC{6v^|7OVx3bKd2%pjf55y;qZ-mWp=nrz)j>psNN{llVn0C19KacB@;}>8&-=|k ze}{nv-qHHstz>zSIgnS;Y4z;_YaxBVu)ojnizPBLEGuh!$%`r(_+OhrERl41tTI^; z)gSxuooCo0`{y=G(16hssQtegn5vI~Z*A4(!f@I$ithgkdkd&2+pT>Z$q@yIl$LH# zWRz|s1sp=8l>c||YGQ1V8rw@!0j zUS5@7RKLnzlWerMHsMM$2a^|)Mv>G$=pxrForYsiQ%43dYYMv_u}zHh`j%Vj=}kL4j)@d^ z2Jsi~;uoc&mGyZT+#~E)X=g}YWAFM|d$=zUbl%rBR5h8l(ufwlr&$WMVeDcS%mDHE z_&ebx^7sI+drZtz{`#xOGIicd#mblCXU`<#FWsyMHCU(i2cOJCP)=&uq^YmB_Znm* zm}{KLN^W}_;3XSM{#ZxwWPlanEfj>|&AKLcF$2bE6wP?Z@iI!my;eR!^5m|~*s_oI zTV|A{&Ta_p1$&>6n55F&x18bfz~!|chg>cU184{OziMl+`~W{^$|hZedCFTV4g4wa zZiIu-&VC7%YNn@*f`rPOO#Lq=5IEI*e?!9{hVc;c?u7i_g=bDBRXlf}CU=?tl?Y$z zI9uM?pJuPznj}hX)-$z%ppIkJQr<&LalySkSN1E|g(KQg;NU3z(s_k<<4mQ=2B$33 z+E@>%h33zCxmxjKlVvweH#o!L%Dc$k@eU^QuwY;ewAOD7QFVC_txI2j0FvK$56apH z`rsEeR&S*UcnJO`*P^J$DzGx~&`*SrV>$oK)jpz{6ld~Aaw=8`?)4Fh#u(Nsn#5z4 z_|AcIV?p@a)I{Iq|cY4vurhl0i}BEszHdt(5FLU}0o` zVRQybI|f#kc`LX{=!`cuapsv_&&myCVQIWK4#7nyJ$3q)O^3 zNWq(cOs?^!%8yv8*sfbTdo^f3c^FZ`WPiW-_E4>*C`!n9Rk#v!;-wIeS3`UkeYw=rZ^FxZ~Tew_fUqA6UoQw*TQMN{*?oa`0PRqC=%6F4B#9m&%LDpg%vo-%`~Q zxJSbqs^#+j#Y?{0n41R+$ohz7x0nZc{MF}uH3a=0+i{OAAm!A8ux zYLTbi=$EPGPim3qH0zyhjncV(4%O&h$5;pnRBD{080Ci~a)^eMVGklhXln`q!-Ks9ht_WPv`PNZWiR4a4y zdBidYnKg#sx%atfx<7S2ERPv)Qq@R6aH-|Bwkfi!w}Hb;V)ErnuyrgRi?35%c(^)+ zgv(x_%j5!{dluYA6gO&pO%d=F{QXX=_da$XC91`*^%apQw%LY`IspJKa)*a%ogk!* z8=I_GB-D`TNZf#RmSxVnyTTKDq|2heQKFZGw7%k-qYaiU5X{vl`OpHV;!u7jN=Kt! z?oPeG%oy`G;OB+t@iqv}k%V+9$s4d$#?4g2S87w^-Lhx4S)$h&rP(R(=M{!>LuCVy zI(kunUc-Fc2RoK6m1)10R7gx3Fw?*At`sHm;HrKmVu_X0kBlL$>?qG6*mWH2DK{8h ztbX9r;!CT0XPM_b>AkE15klL0;Vm=_X*@$g^Z{0k$L`cdD9vgijBHAUB{(m~AdAQB z&Yjsts54rP#B=L_^{m_s8IQ7>0}ydgbqR~?owxzv;FNWEiiTNri|NJ?EstV6_YHs; zrw-nhU@c%c#|=pCEd{=JYJ-(_?NrzM7fJ#3R&-TWO-{Zyz;`|g5}xS*y1AJ-H@0n= zTS{cp$Ea`C@T6R3iik+5v4OQyq@MnRpQo#;xLwT}_buygVO#?Bj;n`~FI^98hv&Yz z*JZI}3Ero>Gyn^&BSN8r6+HOmNG8rKaD6Tb&___EjzViImu&EFR>otr=@Y&|i5Za`Zk7WUfk#1OZ94|>ydtgEN;{s!6uZU8%4J_Q>p1OJcEExx7eH9+INzi|)G?OG-o`ki z73Z%+c`gs~JpTX``0*_{wjPh7^~;)WbqLm{IUr$gKvC*LfxQQKZ4lX)!zHpA0yIvh zaTC9z=i%GiyCRcgO(e7$A_nZRq@&>d=x79S!IiI|!FMlb0JFBXEP{LqMp@LCN^OnM z`+uX`L)?h$4KX$aUB)?~J-Pi>Vvqw}g!U`JE;t8F7vG!PAQo<3mAR3fb1vRRyi9sq z&du}@wY8!iD@IDOqFj1$8I32a&lym;m8`9C;Bb7jmS@j*n~__&PZ?5xB=lOo$Cz8V?64A|% zOGcvPBB}FdJVYETZ)-2f3aeo9Y5F)XdzViJS-I)E)sb^#O<{4_w%!oCIPwW9&DhiE zeJL+XFa=DEzpBV%&l22^7n}`c42U_4FZ+F2L?YWM=FaEjvi$R0z3)n&ioIz~tTih4 zI+kW3&tBlRg3h5CfEI!QEt!W@Mjld`0&0eQlB*}7F6@{IVwn#6roR_j=vNuLh0lg& z>BZRJeQVx=Syj*w{x$#BEPvd1Oj`IXgoPajpanuHtf#R=q`Tula}$OjaPJNTUn0tI zLh+%RrYIu;9utV7`_o(4=m3oKv9yyKd|ryw8Al+klB;f6e@8Ic*!+;i>W-H#Gel6K znjt`Crox-pN4)4v`zwzFjgNaNQuHi-UWElayHS)@mfGKXI*|+o?z-8~q$_q|<{O-D zHvnOdehDFH0jx-|j5lr!i8Y5($-ZYZh-Fp}?rB+zKT-z*wMrX zZ>i{hgc6dU!jsPq8jY<)@zvUlY1|))TKZ#sb!HhxnX78%xVV+*aCJpmpWFGwWmxC- zRiFy#(27BE<*lZUG(&%Z$67*GNQ0p~R7?8}uCKznAje66b$kQALPy@*c5>RgG)=o5 z#aSM@-gMDx&6-UUWc+7U=7`Rt-J143RB@RA@CV5B!zdRIe}MWVIXEJF z`4ECp@RU?mv|=>NIpICp$iAk-jI~g5p6gm0oTZ}>smKf0+%}z3^Rfu+8>T4_h2{7* zBjWZ1nCa&wA{8mzL&UA93)WUdkju2NwdT_~klHF~w-3y$-Ut^r@{entO*6E?%o@A# zN+r1jhHFjcB*m)E7}FuXZo7WCnX=4!u8&K(*qH1J>**)UHHU5^Wl>mE|G-L}KxJ~N6@si)z zGUAc$lweAq!1b@(;)vY_=MOX%9JzI-c)-eCziLsHn&>y^uVCaLEMcm}L}Xz-{YV!b z)H@Ef-;tT+pY{BVW4oAz=w6xJY?#bR?NDP$SkJz6UOSjmlx&_fILAEePezH-W2Sl{T z?Sm03Fem#fxLGew!BWl>GD~ksy-616357G^CK$kyYW)+S`Xa1tu=JDRK+bQQ#T*UKH+wIPQU-STD zHjJHO#g4S?2lQ3)^&D!oTN&D!2M#QwwYqy}cCzLY&7gtcsTL>98%%xVH4<_zl{LP0 zj6ljuyep?{SnS1GD^W|ATbbHN7()Q%x~H>R)hZp@Foki=SV}cXe-BFYMa)~Q+niGw$XSi>DY$ftrPPUdhshlR^O=9J+Z;ULL21AkJ zNVna*v7HQOks@_xl$k}Km&MP>v#}%v8i(xOpbYl$Ywg|3NR4;KZX=>qcrdLJ65p1+ z6j}mzgj*@c@LTcD@?lx7F^y!o9qMQ+(N@YOB1Ny67{((XOY8-+%)t7Ww$TYxw5|Ox z*k$jzQ$e5#r8T+PN<4o!c=mA>sVma_x2Zcg(i^zj^SvW}BO|F&5J{#j(4#{PPTvbD*vm$%r>Y2S)TUDJKbG4@-vBYx&j02!L z)kP5t|M^>W5)@^kKCM28y9}o z%hyU!bbpc9OLaX31{+@% zHSKX(KH*~gOz+pEpgLl(UaGzPcg}M= z&>NWZc3hau=D$lt%<%f0RA&`vvv!x!&O9kI;fF(9D=~L$C-T=J5}P`*&5>^FFD$)lTOtJ9g|Gdv(QNja=4<(0aVp7u(U3wLSD!Z74MqiMYhuq zJ<|xJgZ9>0 z=)+Ul^QUxVj*xpP1j}e!m&RPK0%*SqSzJ2^1<>?NIuO}b{n&9t!-`E^ta@7ERyiO< z{L@J<$hSvZinqtSU^&$oLUI8FF*)(i2ae%LGfo_M`XJ4WjuDLZ3=0UV_NN;(hE$$d zb0l`d;*_ffpURB3D2Qzng1MIkSy(l_3PZ_TL8phBtMVcy{FFNv2XKj(i*@tq8ofoh zFNb^)r1_l|rI<-MuPtVDZvK#|3pOc}*d$9Tv=P>Ioj5{bz{o(r-pmlq18Zc@)1J z#^cCD+|Re%&bzB42QR!nzCd{{GCgPZi@Y zF)`X$5q=2LwO-h=ke@sy1}yVIs`HJsvJ^w7dqK?|#JZpR>Dg9|gpMk1td|g7ThS#c z=#icoh~5vEOwcJlBy$I43<^~3#HJ%+3WEF|W4X{d!6prcq;l==B(<(>P~;p$*!##V zW7nTa4#J7ms3E|2>hQt`qEVcp_aZbNF=DzRrAxgUV+IPBoDQAD^+tVG=?yI?VG zrrI;ehj{6565?XXi>p$oGH(&rSLlZqNPF%9vtv~P!&+KYRmYyy)4C6mKIY>MBM++0 zNb2JIXaF{yY^lqxtE<+$G4dy@8U56IuWWF-5Q3AsCWgKF?zq%vD4HBC#i&H-E{Sa$ z3a;M9>J;n##Wpr5Er;TD*B9W?JUv?Z`j6Bdm+{J<_*D@ST0h7z!Q}|p3VCNn9PxV` z0c36*qvFB;31g0C96!l}H&SB5hz;o?3{PH*BcOwdKc776wZ>SK_gY%+f>3x^3ieY} zG7@WfJxOZ1{due{%A5W)J4Zy7CqgM*(B)WJkX4wzSKS#7f&fTw`RkcFvnj||`|+i| znv>ai-sm8KQ2+TE}sRMg3tZDT~;i9H6Qgu1vcXfI`!BopXpSI4l#W!N5D=Xq}UTu0ni%OV^pAu^9meyk!zh6%6{gtxSi& z@?7nHj#JB$TFYPdMP4mt{M1KuglC4%lztMz6wru%sJp|!bbpR$t5K|$l8Pjq4=vE; zb(1bXjYfI7veTj2u@`EnG%i*0ets;AxdeVAe7fc|vsrRrt;%J02#vq6`E8GgtmNG! zeqH=JL+0xag3aoUM79T+i12c&G;h)Zc%B5-gJsCn!`7)UfUV6QyMs_q2m|e{!M7Le zJMToC1H&CvJszdqPoJhH?(dN%2PR&3L8NZ|><|Y=KBc5vgHR(bT6}Mh<{fxmjQ;$D z2k8ar3r)TNl1_m~X3tj`DyUAOHx-GDH%8V2_zuULrw3UI#P|GDLP63~g3-pYlBq(& zQ?3JUm@!yeE2X!4znFwbni_>Mw1P+n@Azr-2B&W{6gHS2dEHS(>U1ffS;v(P&NzD1 z1B%!CH$lcv=1@etI36ON)U}d0?cW%3*PAFK8oe#JO|OG^=up$lm{veTk13PpImBwd zyYQ$fuj}%tm+=|>>qeRO7wm;cD`Rg1;8`?L9aZ-90F&xF$1S1ZjHOk;+kF- zyuszFGoME?;F&P|12r+oE6=+z;^qMOk^(gIL7$E z20=+3iO65!!(PC?z_lCkD4e&Zj$%YgnHxUq9j=bMBBh`(dEZ;iPed|1A*M0!J&oE| zNF|ew!A>|w^D~*Sr)=skuj`q%O?TW$XqJg}h@cDZeH&T({Bn|h+(M~5xW+&DCAUb# z_f=)`Fcw^xV-DcB6FS$^k+1oV`9>%I$FqS7m<~>{r@0-PXlrUxSo<=4YlTFDcVeJd z`{syiW?+3sa`%t};u#%Cf*D@0-HnxZB5iWvoX})O8PgF5VZ^V^%pt>orw4r22}Fnb zvi8PihHwruy5q8m5Lf16pq zxl&wLIf3mf(>)@-y=geYfmt?IW0OH!i^`J5=~D@4`qkOqVi}Znn>{dK%yqK9e*MEu zQ0dk==|xx00@_j*i@N=i%tf?X_GTMlv(z(TbkUm!!j;KWBbmr2%-7Zutc6)j+X#9Yk^B7*GTs9_FOg_l-(VX|Df|5!43x0K;?7kWsn_s8z zf?2*We3!;+p69uGP3Zd|e8KR&>FpJ*-1l#T@?jt6j|0+L9bt^NF^1sF z=(PinSR(p0q-)CS%A|2mDu7LAbQX+3p~)1ZAnEcari?UC!3bB)y)!*1yzvW`;Ds(? zNkly_0D9gqdI;tjH`r&r2}Pf2FZzifcBLT&32C)qQ>O+-BDL`bC+jD^z0^F%r&k0@ znV-RuOFRmXRP!HIb z@i@^%)Rc^t-DJu=m**n-7iwGA&6xbyJ4HiIfE~@|`%PG$G+SSri27fD0HYV=GDfT{ zr}Tdjn-~nzH#3~@+v^CJR4$I)SXP@?L(tn1<096g&MvV<+ENPJ=?DDeb+ixF&$O&s zKi5svk6XjOObX$X-cY2Sz>F#KQWj?@$8Aje*FCwL6U^1Sy69-}&J!tnXULF1Q#q$+ zouuEl%3Yw1o1Pe{xX2NEJ-LP^X1sTD&$(})sx&{C%W1iE+vvK@Yb>3e`71-N^@rKZ z#@#Zm#f&cI1DVeNrP&F<`lm{P(b|W`@E-TJ-|XUy36$YOBWm$5qn&gYA7j=_0g> zpHZ{gC-)u7b-fA9Z7nF6{rcU(zrb+lqtw~%(F$?SJ!?@tm(mjLFgf4I4ZWAnns!FuWWI4rhse9elKB0wa$6c{+KWhjcC`LOw zew(}|)?20Shbt5wX}#i-sS_b##9OG1o5YP)d?sRf3#Lb~3nX!kRn z5TC~H(nFO7Kb*(pW7z#|=c-S}8PWog}d&l*wNMU`l`^GOxD80S&`Qlw5( zT{V`Y{`yUpgF8vz;Auih4GFqd=@5HzBkM_EdtkWYQMHCoYKU;gVj-%{QvbD5PL}Xk zO`1shXun#>T}g#H0Fdv?kk<5~6M4m3MX+xT>&wN{$$4B!Z^&-6DjgHg<+P?M09|P8 zQk;d@FwmC<inqeMNFf2G}2bVOAn&DihKD~4<4yqy=O zvrX@696UC*7X3jPw*>w&8YR8nDC(0QR)`Az*;bAPk%eHqr9Q8B+slc#yt%U33{4wn zA*j?MlAY*E*Zu~X`rTo9Ajlto1wR_cO^AAs^MhnPI(OBM)%J!9qU!O_4oMShQ7on9 zCGcas7{^s0*%>`RkY})fAeV~=nCTp{OK8d>AW&|%H(OaIG$@Q9Z*05tGOCni@t?vgb@Tidara z?ipUZb}~XJMF-q9YoA`NUh8$D`IN7xWpMpbe%2_dMq0GCz3gaZDGf_Fqj=On)i}K^ z9xbnW{8K(^hbtc3tBu8bP}K88oK>+={>j@S2R3Xb5-V{kcCiI-fv{}7Dk!x10#~6} z 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 Date: Sun, 1 Feb 2026 01:50:03 -0800 Subject: [PATCH 19/22] Remove accidentally committed xlsx test output files --- xlsx/hello-calc.xlsx | Bin 51603 -> 0 bytes xlsx/hello-styles.xlsx | Bin 3881 -> 0 bytes xlsx/widths-and-heights.xlsx | Bin 3853 -> 0 bytes 3 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 xlsx/hello-calc.xlsx delete mode 100644 xlsx/hello-styles.xlsx delete mode 100644 xlsx/widths-and-heights.xlsx diff --git a/xlsx/hello-calc.xlsx b/xlsx/hello-calc.xlsx deleted file mode 100644 index d2b38ff43ee1adacc8331bacc45ec7ab0662792c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 51603 zcmZ^J2RK~M+x8M-5iC}3yL#_LBU541S-P= zfhd96M+kQ}A3HZ6OTz#UJ8yGAe^-~T^g*`{VMx?=NL=qjD1(%tk@EMEe4BW~Gf;Ia zocbzlO3(k?4Y%rboFXzs%Cv9j>s|E*I_+;m)O>0V5jE4{$EG+I_^ub9tZV9f_E1z= zrBh?K$*AT)b$usJ;&V!$Bfc{8eOqC4kCvYdPwPeC>844ubBURkHOJ};tT1BVEwxkg zp4^emY#w~_b5v%+%h7xHUD@0mDvHYkt&$=TfveeUF=vF8Vj{vNgc+ zfpZsya5Nec7=riB`TDyoA5}%_b0ap`aUhh3&qgXynBZ@Af;L^nkix8)9|{Fm^)EgV zyA>DEaMbr%0Yq7%+zdK=*)%)QI-<0Dz~X+lXUu=GHC=R-wj}z1<;9MK7BU0_>KK}j z<61kKM_JAeeK-}nSjug_ecb-v=Uc`3AR_>ntMDxli2T3K*T&t;?w<*#BAtPq*t2xZ zqp|30Ml2dWp5;#3D^7V zWPeBs2s)+sdMoPf!~LBUJkpmG{8L1nCAg|(Go4vE>CVLN#tiSczu+^MJnkQi+JtCp zRQ|lroyFiEz1M~LfRA9A6_7~So}peBj#Fs>*+#&NC4|M9QgTxw{M5YP1>AzP%ZA#!Q*Nie4zr9Wxx2{RR@ zexQuLXKNEEbCz~LIOJB3=s3NySh2%p zY#b}N?#o!z^nW(QD0ngD#vWV#=U6!m|5;LWv5!D4%`1%C(_7^Jkz?P6G*@L_%GCT~ zXHe-x(so%<)S!^`T#giB-+msR)qGwKzHUd;$cxDy?^Jy-x_>3%^zC8cQa*?1gWRo8 zIUmjUHVOWNr~kcuZv#A8dfB=B&%I0Y;jYwgpiL7O1S0-VjNlEjDs@n&Q}|B!_5C^UM>) zDL6Gw>C6snZ#&W5wL1;}k%coEQF&yHn4SyLJN&XQ;o_LRxKp1sj5e1Z#SPHeLb~!MkhI0S^RRcCS$lv9E$enLZ_Yn$c$OpbR0U7{!J6L(y*&6tGIl4Xd zzEOY#MuD^g1_5oiMQkqU%h1|r1PSlrpMmthDdG~>#g8rxbp5myed%~wQn6@q&sNlf zRz*+E+4s5Rt)z3;u-jeMeKT2vB*7_GFa0R%EAl89c34-7Bx;z`R^c=yZh6g|d`uf< z&4vw$GDRg7Nk9F^`flUC|D|GGpvw^buyZ4SP5rp@;?{pm>|bF@kVD@5IUrkYU{)mm z_S+}G#qJ-6Ul@FG0?2ON=1bUqGna@FNlh04Z_bnYKBMH4==FNHFt)kD)uTQq+-)M5 znpax>)U;3X>8;I@+$n1BHZ%Tv1|~CRHaYuA<^k7VUy@Nc6H*%u1+AWzH4Ox>E4Y$r z;H%tO_YMUY29b9>B(N*7oYsNA68}a=$L&+K^U9#^sJ1uUL?~0YLk;t(mp=YWeLu5E zax6Kw=yjJ41vub)Q7@WtKb}=%5@_fy*-Ear;U(UnK1i6&4Ja_p(;i+yuh9ACb)31pI<|j<{nG0 zH4q}ZR>opgXp+jr4PY®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 z z=8;#z?0VU6OA$$?j!@4c2JM+q%e|U}S9FLrQ(c!K8Nx7yN{fJx$y>Gdt zC2vGToyj*Qp9FBB5G*OI$5RK{=>wFTM5`m1Bd;lk3YwdK>eDcNAAg`wKNya0(()Q#$g2S?H< z4OB#(1O@Lm4ih6X{S88qI33*o$I^R9O~0mS7JSK?x~K9I(b{vv=T~vVh17Do;zf|v zD`3!J>$L5+P`2gZ@3GTb;@vZx@a8LWd9bD!@VXqMa@RsieA}cjTao_O+>9=}n)OIh zT4MH~h2POJoU|AhH_84tEyQRVsjW3OX6tkkIOIn5I|8zDiepP3FL9F>e|6!J^^M4H zD-TXpx1Eza8dXFv>t|7k3OEp~c!Hu=i3#C(R1%5*to>%oX<=aEFGkW%Y%WqOGC5C- zVYGLc>3!SG>*g)c>52LpU*AKg%?vqvw;0J?{^AE$0E?Z8E;0SAS?r^CMsQVxaf6Kr@yT?}gS~UKo4<6in|M3=0M;9sn)9?P z!O)-x{rC1=V%u}zc-Z#M7@Zp=K1KV(CbDH>c3xwMy?@%Y0#8XOU4+ndE6DI@^B{PenhG{tU??oiH3wmp zcCgX>3#|6g;`Z1vELE$8oH)8;B#8^Nt=L>~9t_PE%?3c)I5EE1;x7`ZVwX3Hzg+S8 z**y#^VNmsxOFf6_9qG!r!V*Cro%C8whv8IJNnFb|>u0CnX~RGQV$C7!E5Bo6RfRc^ zjvpUG9GDr5C-0xfT`X!CRB_d~#0ntNtgFZm+lrW~_?GJv^ z@O8EeL3JtYs7%OXNtCu}Y%63V?o%TGG&7_BxBEO<-=RW>=i^m-H!iw}hY9uweBCkW zsj6ttOJ5rWMW{-`n&_>c0LO!x6&k{bPyrX>vfzS6)9kpI|T{g14Ot1em(_)EFs?_E3v{oQt zNH1M=%^04RJSHv1!%ayTDo8xVsnqNrYOk8MbzyBd*kB4YJghPN?IQzKfF^ zd7W-v2`;bFMlA0bs^iY@h&rYueYSqCSF0blj-B5I@WaJaVr{J#FCYZxBdNP5{^Zhe z_$B8XBuFY}G^@2)omHYrOg_ZE_hYHK3BMQ8Js+@_qtw;(TC-#}v&zc6(?8oJaVfCt zO(@-%1EMIM*E2bz5uP*gz%;jTMs|5)fS{WZ+`Az0<^h37cM84jRt=#5dOwAcv6BH0 zDfF&{lk*&$wjts!f?$Nsm#6}8s1@Qi1;LEp2Q<^}gb;nKlS$KaGI%!v{osO1W1EXH zF488i4w|UOw5m3?eG-RK&2)&TrLiUwwfe9PSlo9YGU7Ps1c`#5{h1+A)c9N77cJA= zfqUI6agcH7BLUgo2q_4dYzQ(%B;?!V#y`59+{G7=2|l)0)DZVVBniBg$XhoqL*b{u zdRKS*UD7d9eeTW&M5)T_`y&l$Pg$Du^153rrP+8Mr$$^k^WFB*$zS_6p+B8ccjCM| z$u|s*o{uCYr6f{ybsPHOY0j&w`#c)05ja}>%G)j4#qIZk`g&~a!Xnqt@o%&)xt|^y z>)>t9oqMFQm7%UTrlG?d#~E&(>roc68>d2eVCmkZ;e+Per|nvOTQpeGJoo@F62de5 zo!RVBOUj?!=zK?00%IWiijI-m>9Sb?`8dDInneT)lTuvKclfXyD=iv6`^h92(%n&?Zl?2wDC&$dK=!6j6{mFWJ@x>lpdq(#astBHa^IAV(Ti-D|0L9x1zfV zPk!fACZx~Ki`EKmCf$EgKuEl|SkNTu`n6!P%lgRqVfVPFdnNap2c7#eDr-;*sm|e+ zwL0T3kCx8eqHdkZwtg~P-)Z(@xc-i5|8Vg>>Ad)^PS>vVz>EO(U2)wLWoGT{gpcKW zns^%icQ}PQfsmL5TYWi#;TpfL(J{9~_VBbH&-s-OY=eU_pR^>3aS=~<+^Q3B^%=qV zB9wBI+E&99H9KL%(ym!;>v~QTBHX1b%M%yDSSzr$X2j=`%i@7@{ynR%S6dcx74?~< z>zim417W1sD_4>E{_WTWzKrb31f0d=Gk7J@+B*b-_M>L~7L@x+%)$!~L|0S}Wq3^0 zGLF3=)>Ak7R@f53o62<#hvJ)l+ z*X)NRw?$H*%bPAyrC9-mMyY0^(oSdsoqT{Eagi1*WWyRQH*>pN+> za}kk3N2xQQ}TDJDnM&vR?B#OcGP= zY_)zxqk>aMMw+WHdb|u6Lk&9!y`2&G$T7@(#BKbxF2w|)G*a%prOvMAC4+Ut`WFR` zbG3^FAD-U(S|FIscI13m!l%c7tcz^Q`^H9i@@NRMs_Yvjv9q=>*ga!t^`6jBXWc41 zFeYuckZxlNOe3ulS2{@X57yN&pv4v10K(Q;f~s4A#ug0WLg#{ga0+2vGz3H4h;wsv z9u`P~q5H+zXS*>oGXm$?ZRXXtDvwptgx8eEvW2^(b!u!ie0fe2?sEIN*Qi9Lml00k zbG2j4f`#|QW|hkAyEgXCW1e(`k>s<=_#$%yOSLV}AbNsiP3cyXhzDv?1K)t+2tT*jMr$_cSx3Ly zU*{>#4ew4|r0adK3XU*w0O=JSbDt75Ao$LNN?{Nm?z8S?lenr%bR`uw-dGbf4A z2%&QOiQ9?ZN0k%#_igN_#)J`$#pf8WM15;m*q{AsZOVF`NN~-f3W_Y%0+a!|8>OVP zNLm{uPi2}(-Oh9+<}VIkgG|cdrE_8FM{Uz&Jlw*6j84r6@a(Ghc#fA{jXGaq8W%g; zK-W#(Y9Z0i(ymMjNnp&9>Xsu0 zMKCP5JX;zvP#@aL_a;aNuk^dPa&3*OxmjO5++KC7lC_&AJgv>iEnN2VIgNJUHYZ4j zk;|m;PHUegoUDk3z~!nlF;Sv5A~`lw+dZk?V`=R;_PZ{aMW3rXu zL>;e%FDz~N^TuUWC#0WlDwC=GAE-0bJA%VbgkevGr!g8AaH(H4J<-*dzaLj!6KG)6 zKIi_*yec~Nf{9YUu9ksO_mi?ZuHy{#Hs>pP@@&eFvYbZI|o% zz}SQd2096uPWrJ|-7dTxy59{o2di&neSF8Nq*Nsrf|`o4aJjA>c1Y+Ud53wB+~>`o zF^tj7-%fm!6PfjK3ccCn7Nj^8b6NXs-3!($&)JI?+P_b~4hqJ=C+}4YCYQ{*QCAF) z*6LPNcw`FCe;J{Xat`%pWHXpVZ$?nuuU!iDC$y+B6h$1c3U z(sX48!86!)%l+|1Pt(3xspNr}=s^3?gvTg&<7oota0;DYD7%_33wr{??!+!wuwRwI z7d^fj7PC-luye>^-uvh!rf`>_KL9IU9@yPGr(5>+D4h#(1VbOn5-6>B%4DY30uF_C zME?p3$4&rbeKTmf)R+K#m;lN9De6Ax!auZK7e2QDqn}@GzcC`&SyS^a=$u2FZuew) z9Ty{iMNN(E3eNOI($Oi&LSwrjNi-s*xVUHFy1{Pg3c$AcO{7z^PkK+}YGSR#FH9o; z1xRSP8!lLWvSdp$>&!3i@{h^6uMd+tqFfCX!oKip%&o$_o#LYFx zO;A7S^U*8kQZI=jlGUt_lC>x=9!JT-eU7Qv#o!eFYN8Jp+I%%%8sa}r z-qk0^i$1}#+v$Zbi$hh)@zCaJ6CScah^$o0MIHa`;ZHWiwoh$L;fE zs$0BkbF^K94aME}RicY*?C(;Ex{lRHsIT*WF5tW(1Urw?jZw zTY#vZRR|O)f4?tHH-B(LsJx@+1h0E;gvfA5MIBdz!bJAtdcv?l!PBI8@h?L|mi>yD`? z?heL|+#`NiD@{1$GjAvQ2U%D1Pu;SR9Me!xz6ajy1j>82O7~^iK@@Fhy}SS{w?5X@ zJpAGGf!l|TCRQ)U^(AywXCn>BXkEC5Pp?gsTs@+n>9?GlA-=fSS-9g*# zp%HejDNbXnewR&w@Zh{AXj-{3(6o=sL3)30v?{Re1a5n?fkmEtD6n_9c8pKqX#_!F zv(!W_l%51iCwt;)>eF%TmFYMulnhu7=!ZXhCQUl(YTm`2TPZZ?o&rMu;;*QyStJtr z4iGv+)U}=p^TKsq#LMk>o(=B@R)fC@-7n1?BdLiRBWQk5d{%!IQ)vcMJnZt6B;8H} zaJ5r9=k8U#i7zmEbL6}Fui58%PPwijuXwlF>AEzYGmMdd!2a?@Infc6@*4|KP($*@ zyU3k^yYV%>@6(E`U2^k+PzN-X-Mcb*f)@8gO9i*K$)`RPtJxY91c1S9YEb*NfMDA| z5^+hy%;--$*ytG*1KdL&6W~V5`UANiTQENam0Wc|-Dcsn&E%W*T)i$F;kNTI`pdR{ zR(%aoY||~dx(E{t>+gHw6pETnm8vklfEPjtsDluSsHd8{y(a{k`K&iy$?#NN_>b<5 z4f;8QJM{_apPrP3C=0<3|4IduJZ*JWK(Fva9aq-*+51Pkn)fr+xBQm%rVbS|dbZa6 zcvlXuH!gx-3@%d7#b|f&{?Q+j+i&gyQQNeb-0?PdQEFS|2hV>VO7Frpt$MJjIzdW2 zCmzh%DFkmbf{s{ zaQg7&tYoN{=k4@v!~-g|eh4_A+i-QV-;`vBP|AU#b}Y2wdq+$o-Pgqn1AY(5J8={t27Qmcl({|j zX!}PeS+1ZC$vt}SQ&tn&q8Gf*<{Lm5*xbT%v&4t1^h}rD&?RQ@mw%Xe3pS(PH z^E3JzRX3XmrQA)+uYW}cbW>v8ulLKI$Sd9OkNk`3lKv4=NwR<6%dXd24VrKttI0`7 z7~o$1>!&h`_|8hh$2-u;JMfxmn6FcS4e_S_dj}+kVEk(U@%89`{IjVR1iN+gak%8~ z;~Q}L-^2dK@YrwkKo4xL1REj$V_0R>-TxeB=j;2=gZ@4GzMx`G7;JZjjD&>lAEQ%9 z|Kp$zKK@Ss9RA4C{{`FHCmHhXB09QMICr6V&e>ZQJ zfPY@!-+Pc0P_>DI+hYL7qxsixc&&`mfXfBC1O$e8I{iO~Mq0^~9KfcJVfcxE4Bevo zU#8jn_;~#Dxc_~r|C|e~@Gl(q|9xwRw;EWM;8;CyYuWyBOx6D!M&$Uvj{N_7r)mFV zKbvY6)cE+*`^!NThYh~poXZD?QX7=nEeH}2_Q4k131uD!@x<(03wu6#@ zK-^>?&=KHYb5$2-4}`OarIEKQ0%akNc5*DZ+^k0`!Bn$)RJRarK7BpfYUop|fmQ=0 zZbG!bt=5C4z8_kdEkU7j>e0iDsltTR8INHub5#va)#eM1@KIYjgvnUy~pS$??uuwSiz$A?sk}B!L@+G!}E`1@clHNM_yTs^aCt zNppnQS@9KnZ+sOe0kMWj!7w$5XZa4frV{4&FBSwBnd{oNK|2WB#HET zpcG~*L#Ma8cs+4lLLL=+Y5mB#B1f)I6_ssKneNWU6m!?kHjnm#{kjyBLzbSNIMShP zyX1T4Uc^L?@kzed`1+|6S}TLK;m+)I7%-v4a;gcc|2x*8tylx$-C5MY6Wql{-`&L( z^~bi^uawYk7`&+mwqZb8nD&}kxw;bbkr7+x0G6Orn_8zC_U+bV%p)sQwHKZ@u}iBg zF1_;dZ@8#5e}Ltq9Icji-L#<`_HxB{34N*~7kkDKwxy)eiDxH$)~mfO6_9uqNLU1u zk3U&Aa`Tg-$T3`eknt(rMW3U>A~te&A(WG8(Yoxo7L~rMUg6<<7tpy&lHf@1XA4a) zR6s|t>J^}$ssHJFTivEMGtc89+t`67R41MtVZ(;a^CV46Uo`NK0oAvBj1>H~@G;!7 zucd5Ra&0iZf?~0D|9y`!B&hfScZGmKAm+XIwQ+Gr5HFk*Z-A_aFf|S?3TqRPS82~i znqXAfF0$bq$HjE?njI5j)4Ol2<`tM!E|-*l9N4^7v4$ER&3_$o%1p2{Ef&ob2Ri!U z{NstxC#G}LabygsETSK11hAwR9`?S(+)qT(x|nbl3DuEvW3Rnw4jpIG)y^M=3Sl_W zVYB#{GICX(J~8R&sb1FakVuVZAb2oet~5l7D=s}Q*h>>tq|r_5gnTAYZB{bRpR#_g&TI6xba?ruqvoWoP*o= zz*Z8Ve*WM_nE~>RcVdwDGQP!Eo6TB6O2Pd&@ua?am{DF?{qn}2Ul61x$~&?*o017S zXwQ}t;%gtA`R_dXv8WzE?s+dP*F&uMct(t~EtnY{($U?bGXhJ66@KOC)OcyYhPPrV z3^(8qv}f_}9{#yf_~NtQX6>CB`WaHYSKqj6y2>0HYc-lz&djeqRSUoUG{ft1xlZOf zXggJh8B77B%F-R-c#yuw%Vgz7fju2k5QuiiMx1C_5Z|o#5<(X^rJ(+0^@p=2RZd5u%SS+Jf}XZ#)gG&t;yw;-W*-WZ}&uX7Kv-?k4N_qN;wE) zDP5;WkmuYcVibX(snf>ZK zoWg-!7O@;Sgdyn)&R;N5?dkW`|5-OD?P#Ayn0|_Bjj)h!C-v4F7wu1CtWd-%GUhta zhzZi0yhCo>)g&Ber$_hMp{m=L?cF}BC9~wjy-4xDB`Ho_;8O7b@v632;d6QDkScr$ zRfnfHQu_C(0UQUR9S>Bz$hS-8;lM@~@F7YwV1Tl-a!0@~d$`*>+oFg?ARVK^P|wQ* znyLvNA25Wm*=YMhX2@4ShTZ2$X-lHo2jAj{;b&9rm$5j4**W-GS2hiOP2|lW8SuST zrvM7PbyF{fn%*zYD%I=AQ(Pts=k&>u2hxSy2_t;RvCf$l0wP>v4=aqG-Gx2C;_eNj zJDMm+OTTJZXJ6tBGkrOh^-XK>X-QQe}9^@)vhk7<>C$#MilJJ+x$gRI(1KDb(n<008`W(-2iO(WNn`Dp&T`}j$h z+10Uuzwiq@Vsbp0@lr2P{m{(7=O++UZ>&@q%*Pw;u|uaUNYo>AmHF@AlQpCid{nix z&@`Uss`SyRu|rDsN@lBnb;CKH_alVocUc`LB)^FmrDU%tkLoerMpHmvx%DwU;mhrG zFB0NoHIrlwu`?&1W-6b)6j0~?<4WXmmvMUcxyNR@9IMu!+of)Jevzx6KQbSvaXO7m zdZGT*gYW9+`af&E(NK>wN?!@v2yt(KmPDa$ZdjI?%{$)kkjSx}zs5i5S#@K@EJ&MM znk*(IG~%}Nh{vQG7SjB3V#inUQ!rTNfFIr<1A@|guTuTD zGGE0PFqb@f5H<)4{%qaUFTs3sI);w(sY9zU&!R9_()S1`n|3NkK`%XvW85g|2Xw2 zwOPAqRabsfcuZSvsAUq0Q@(GqX+ygiAKPqrtXIgqw%K1S1vA$DHb;3xKr^AdriRS| z!M&>d-BL|Zb2F^v?xb|=>@i>0UhMnYqmFxzhp74 z)d7g*?I~ZH+||$65DATpKVM~kDDVZ@h`8BVb84+7lxOwYY2_qx`<#t$UtEptzMo}p zWT~luwadIM_rWrH}ZQ0GoV(eQbwpw;&Q*G>p}~1cWYpQ~L{4 z=PgOj<54LIKDV#KnR$~OVmzlAMAm%MlXao)k(TGC=K7q=2MH090woecIN4)VbOH-9 zCyrIs5=of-M77X@4)=+Yy37LF=<$GAf_8z@5b{+)+AyBI9og4F4fYDhRJDk98gwM; zFGe*%y#We#AUUs}mKs%xmZR(bq?q={J-))&P(9nsObsZrhm%W%X*V-9C3)rZ-Y(wC zn_3%1a-F(jZ%*E3yQE$tcnrleb9pbx26X!T&zS10ma5*dXH9KMeJ|FF2eW zBB{66go-1Of<2S!e!mB2)omCyC}A(`_vE_k^jH5lIQi|OLx7^$ak@PR?!phBnK+GX zuL%`#pdoG7LF>yU5phXKR@(gTuQ7AISFSwn_~^E*SfBU@;S z%Gk#i5>g?uhVXxs{ha@Quj{*JdEa}U_j&I7Sw=?#Om+eU0v!Y4vaejH1Sg@SNI)P? zG7yLf_|{y}#n~O{>~3M;<%&cjpq@^Sh59Ypq;Q7XwG&!68}r$lk(MLxTXof5P-3Tr z2ioe~sT&5QsQ@**6a+oVBvn#f!ST(VdaYR8PQtRGw6*v%zus0JT zNbGCL$2yzwp0!#5kxe8HLrHPY7^T4=V*#rmxkSF-any);1-{&e_9Sy|64h~%p`_Uc zxt;Ihzj27!k5g9-x6MOUDgHWaND}Vx_)3WJ zMOy8(<(moXa7lE`wRNV;MUEWrE34Wf9=SOiQp{f$ZTZ{>_U)EU3jSVlnyEw1_Pfvh z=&5=<7BkIKI=^BK+Kx34-rYq7yt8q!)^T%jMgO*Q z>?b8O8@jI>fNdFo7KZ)DEL~l<_{fNMLWR-BUEZtahYl3 zUpB(xy#5wXvegLfTB#%1tQE3vM|38G)U*K52iq#f{#u#hkYZ$C1k z!{&Qgp_ACSK*O`#%ibphg{&nWz$m|_MryHQ3G)4B9-*N;DxNvtFMz|nN|&0S%Y&FO zS1Un_DS!9BscBRCw!r0b=~aj4P)&m9I146Zfh%!VTvOLC3e-S&7tTMi^e)t*pHRMX z;m2@#CB<^xL4OPRq?5b=UtwSn==gqrtzF!Z+YZO+IyAr-UJWk`st-As(TIi{B`dOM zvS1yjL^QNp9OGlsiFem>3yrE)N-N$CZr-i@fgb&w-xPe-gug5`#*-lybfQP0VEW#F zjOS-#$>>rTg?p&^Fr+GZeT~U!2@W(aM(jlb_2itG>#tkxO)+Sx=Z{JWB(r;l%;BTT z$rZW!MZ_a#`c8dua{y!W4cS-$E|emgoO$6chDIU~y17JS?@xPwU07?(+r!F}XqS8mj0!CNxoLUq~r z?HK)uql>G!=O27G>%y4jF32v=7M2@IZHU_wjkAX{&xuN>PgkcsL(EN4?(lR}z7zmY z7H&w#!`fvoza;S)*wY{dfoOKmK(|g7#>cpYS*)KIceG0#nk`9g4MRw_lX_`S2@fRFSIS~!>2vI`a{LN5k;Ht=z2WF;L5*8 z4G=iU)A3Nc3r$=z4+S9vn_fn1H_}`=^D5hKr^*LQ-gXk zEY|8iuy5q6AU&c2Db3S}_Te}9Q5(?|yA=#}$mFt(s4I)Ij;h0*z>DDMH79=xyj62w zG8M!()-uI|sRa9ok$v{e_+xP#XZ$!1GsgK*C7%$-WL~8~=>wU^7;N;YXGb#yY1v0* ztE_A6A;yi9nGXaeDXM82^?>D}m6f~GxVP>6*mQ#{Pyr#gKAWD6I(=m$!kt{%>Cm6qMLYa#$#UZA2*$+A;NpU zqHiR<8Hs?Y=jipPk=1xe21SXnt&-u+!kXCTBb6u52X{QccTXm0wljMS82ct!9@72X1MxbJ% zZ&~#1PSwU;QTPTq$kp+S+?S>Sj)DNb=zntM;o|0C<>KP7C9f!*`mNLi*g7+8YDRa= z(CLo6Y85vx#j^0N9#oV*-ZHrE6+)a>LWFq@KO-YkHRy}i&48g*yX0&BlH;iy19M1v z4$BP71bwh-9)KUeGaE(AUSi)W$h9oUk@zW0ibXwzt*{TmB7*C@#~Uv@j*58h+a`@H zeMI?!QN_=~kY2K1u1G>`stIo9wtu`-(<^;^Qp#H)Yh^fkeq9BpDdL4 zRX0Nj559`GE;yW@-&OI^qPEqOf$aOIx`vibAn60P#eurUw*T1vF#arav8`Lm|6|67 zI%n+zCK#@qapPrHPoWRUTId$aid}6jz9O>^rIww*=`9-9zPuJgOv|)0uuzr8ptADq zbyyH{ic=8ZsT(tya>c!$rjyUGCC6HJ4iyf&mg@a3L7QS2sC1D*X|9AgMXH_jX97Ga z6WVT*o6t4sI5@O~HNL%$wQCMox|d%*1ak5N+h4M+4$XMx?D{?K-NZdzg3_lMB_F!` zdRa-{C%~^xP1Zfhr#`vhmC`MOH2GL8Jdc{Qun716+s?P>-Ub4D-P^9%k;`VGxop5< zM^Q)PMz^OZGzy7yKim``Ylc?zfbzP5hvmrGZ3ui@hOt^Mjoh@Mo78iD=lTll!xakb zyD=HdxAR`u>8v!toSyk#VSH9l6$SpD_-)=wAJj8A5`TVm57@ ziLk;KcF~DuQ*T;+|ErU_;s%Gvvt%BUsL0QYU6X-X`Q{mJ`clF-`ETkQr7?Qyw-UJ6 zRGx+Pbu|&z8hLM+k>qa*c;HYvS2dq$Dy4QSC480`VPd-w*lAM><%}n(({mXe%d2jd zDv^Y&kR&Hgten2h?nCm%t$4L0>3LV|Cshw#`0x<2gTVw|+r@0>>i;sf)vI^bw7ezx zug-nY(&65{1Hc3Z{RAB8 Date: Sun, 1 Feb 2026 02:31:41 -0800 Subject: [PATCH 20/22] Force GitHub to recalculate diff From 9f5ae286463bae25f9528c180016ce8ee90216ab Mon Sep 17 00:00:00 2001 From: BrianHung Date: Wed, 11 Feb 2026 03:06:57 -0800 Subject: [PATCH 21/22] Fix duplicate match arms and test module order for lint/CI --- base/src/expressions/parser/static_analysis.rs | 6 ------ base/src/functions/mod.rs | 5 ----- base/src/test/mod.rs | 2 +- 3 files changed, 1 insertion(+), 12 deletions(-) diff --git a/base/src/expressions/parser/static_analysis.rs b/base/src/expressions/parser/static_analysis.rs index 7a3f24fb3..c22206026 100644 --- a/base/src/expressions/parser/static_analysis.rs +++ b/base/src/expressions/parser/static_analysis.rs @@ -849,7 +849,6 @@ fn get_function_args_signature(kind: &Function, arg_count: usize) -> Vec args_signature_scalars(arg_count, 1, 0), Function::Sech => args_signature_scalars(arg_count, 1, 0), Function::Exp => args_signature_scalars(arg_count, 1, 0), - Function::Fact => args_signature_scalars(arg_count, 1, 0), Function::Factdouble => args_signature_scalars(arg_count, 1, 0), Function::Sign => args_signature_scalars(arg_count, 1, 0), Function::Radians => args_signature_scalars(arg_count, 1, 0), @@ -874,8 +873,6 @@ fn get_function_args_signature(kind: &Function, arg_count: usize) -> Vec args_signature_scalars(arg_count, 2, 0), Function::Roman => args_signature_scalars(arg_count, 1, 1), Function::Arabic => args_signature_scalars(arg_count, 1, 0), - Function::Combin => args_signature_scalars(arg_count, 2, 0), - Function::Combina => args_signature_scalars(arg_count, 2, 0), Function::Sumsq => vec![Signature::Vector; arg_count], Function::N => args_signature_scalars(arg_count, 1, 0), Function::Sheets => args_signature_scalars(arg_count, 0, 1), @@ -1248,7 +1245,6 @@ fn static_analysis_on_function(kind: &Function, args: &[Node]) -> StaticResult { Function::Sec => scalar_arguments(args), Function::Sech => scalar_arguments(args), Function::Exp => scalar_arguments(args), - Function::Fact => scalar_arguments(args), Function::Factdouble => scalar_arguments(args), Function::Sign => scalar_arguments(args), Function::Radians => scalar_arguments(args), @@ -1273,8 +1269,6 @@ fn static_analysis_on_function(kind: &Function, args: &[Node]) -> StaticResult { Function::Decimal => scalar_arguments(args), Function::Roman => scalar_arguments(args), Function::Arabic => scalar_arguments(args), - Function::Combin => scalar_arguments(args), - Function::Combina => scalar_arguments(args), Function::Sumsq => StaticResult::Scalar, Function::N => scalar_arguments(args), Function::Sheets => scalar_arguments(args), diff --git a/base/src/functions/mod.rs b/base/src/functions/mod.rs index b026ab59e..a9e00f676 100644 --- a/base/src/functions/mod.rs +++ b/base/src/functions/mod.rs @@ -1902,7 +1902,6 @@ impl<'a> Model<'a> { Function::Sec => self.fn_sec(args, cell), Function::Sech => self.fn_sech(args, cell), Function::Exp => self.fn_exp(args, cell), - Function::Fact => self.fn_fact(args, cell), Function::Factdouble => self.fn_factdouble(args, cell), Function::Sign => self.fn_sign(args, cell), Function::Radians => self.fn_radians(args, cell), @@ -1927,10 +1926,6 @@ impl<'a> Model<'a> { Function::Decimal => self.fn_decimal(args, cell), Function::Roman => self.fn_roman(args, cell), Function::Arabic => self.fn_arabic(args, cell), - Function::Combin => self.fn_combin(args, cell), - Function::Combina => self.fn_combina(args, cell), - Function::Permut => self.fn_permut(args, cell), - Function::Permutationa => self.fn_permutationa(args, cell), Function::Sumsq => self.fn_sumsq(args, cell), Function::N => self.fn_n(args, cell), Function::Cell => self.fn_cell(args, cell), diff --git a/base/src/test/mod.rs b/base/src/test/mod.rs index e02b526ad..d4cc91de5 100644 --- a/base/src/test/mod.rs +++ b/base/src/test/mod.rs @@ -77,8 +77,8 @@ mod test_extend; mod test_floor; mod test_fn_combin; mod test_fn_combina; -mod test_fn_fact; mod test_fn_datevalue_timevalue; +mod test_fn_fact; mod test_fn_fv; mod test_fn_permut; mod test_fn_permutationa; From 80b0339c8dc75270121d11cf811d5d8c68bb2792 Mon Sep 17 00:00:00 2001 From: BrianHung Date: Thu, 12 Feb 2026 01:47:20 -0800 Subject: [PATCH 22/22] Fix COMBINA overflow: cap n/k and check infinity to prevent 6h test hang - Add MAX_N (1e15) and MAX_K (1e6) early return for #NUM! on huge args - Check result.is_infinite() in loop and return #NUM! - Update combina_comprehensive to expect #NUM! for COMBINA(0,3) to match existing COMBIN_COMBINA.xlsx round-trip test --- base/src/functions/mathematical.rs | 9 +++++++++ base/src/test/test_fn_combina.rs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/base/src/functions/mathematical.rs b/base/src/functions/mathematical.rs index c06e97922..c8e745d31 100644 --- a/base/src/functions/mathematical.rs +++ b/base/src/functions/mathematical.rs @@ -1663,11 +1663,20 @@ impl<'a> Model<'a> { message: "Arguments must be non-negative integers".to_string(), }; } + // Avoid huge loops and overflow: Excel returns #NUM! for very large n or k + const MAX_N: f64 = 1e15; + const MAX_K: f64 = 1e6; + if n > MAX_N || k > MAX_K { + return CalcResult::new_error(Error::NUM, cell, "overflow".to_string()); + } let k = k as usize; let mut result = 1.0; for i in 0..k { let t = i as f64; result *= (n + t) / (t + 1.0); + if result.is_infinite() { + return CalcResult::new_error(Error::NUM, cell, "overflow".to_string()); + } } CalcResult::Number(result) } diff --git a/base/src/test/test_fn_combina.rs b/base/src/test/test_fn_combina.rs index bfcfcd3f8..5563b5888 100644 --- a/base/src/test/test_fn_combina.rs +++ b/base/src/test/test_fn_combina.rs @@ -17,7 +17,7 @@ fn combina_comprehensive() { model.evaluate(); assert_eq!(model._get_text("A1"), *"1"); - assert_eq!(model._get_text("A2"), *"0"); + assert_eq!(model._get_text("A2"), *"#NUM!"); assert_eq!(model._get_text("A3"), *"20"); assert_eq!(model._get_text("A4"), *"3"); assert_eq!(model._get_text("A5"), *"10");