Chris@0: widths = $widths; Chris@0: } Chris@0: Chris@0: public function paddingSpace( Chris@0: $paddingInEachCell, Chris@0: $extraPaddingAtEndOfLine = 0, Chris@0: $extraPaddingAtBeginningOfLine = 0 Chris@0: ) { Chris@0: return ($extraPaddingAtBeginningOfLine + $extraPaddingAtEndOfLine + (count($this->widths) * $paddingInEachCell)); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Find all of the columns that are shorter than the specified threshold. Chris@0: */ Chris@0: public function findShortColumns($thresholdWidth) Chris@0: { Chris@0: $thresholdWidths = array_fill_keys(array_keys($this->widths), $thresholdWidth); Chris@0: Chris@0: return $this->findColumnsUnderThreshold($thresholdWidths); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Find all of the columns that are shorter than the corresponding minimum widths. Chris@0: */ Chris@0: public function findUndersizedColumns($minimumWidths) Chris@0: { Chris@0: return $this->findColumnsUnderThreshold($minimumWidths->widths()); Chris@0: } Chris@0: Chris@0: protected function findColumnsUnderThreshold(array $thresholdWidths) Chris@0: { Chris@0: $shortColWidths = []; Chris@0: foreach ($this->widths as $key => $maxLength) { Chris@0: if (isset($thresholdWidths[$key]) && ($maxLength <= $thresholdWidths[$key])) { Chris@0: $shortColWidths[$key] = $maxLength; Chris@0: } Chris@0: } Chris@0: Chris@0: return new ColumnWidths($shortColWidths); Chris@0: } Chris@0: Chris@0: /** Chris@0: * If the widths specified by this object do not fit within the Chris@0: * provided avaiable width, then reduce them all proportionally. Chris@0: */ Chris@0: public function adjustMinimumWidths($availableWidth, $dataCellWidths) Chris@0: { Chris@0: $result = $this->selectColumns($dataCellWidths->keys()); Chris@0: if ($result->isEmpty()) { Chris@0: return $result; Chris@0: } Chris@0: $numberOfColumns = $dataCellWidths->count(); Chris@0: Chris@0: // How many unspecified columns are there? Chris@0: $unspecifiedColumns = $numberOfColumns - $result->count(); Chris@0: $averageWidth = $this->averageWidth($availableWidth); Chris@0: Chris@0: // Reserve some space for the columns that have no minimum. Chris@0: // Make sure they collectively get at least half of the average Chris@0: // width for each column. Or should it be a quarter? Chris@0: $reservedSpacePerColumn = ($averageWidth / 2); Chris@0: $reservedSpace = $reservedSpacePerColumn * $unspecifiedColumns; Chris@0: Chris@0: // Calculate how much of the available space is remaining for use by Chris@0: // the minimum column widths after the reserved space is accounted for. Chris@0: $remainingAvailable = $availableWidth - $reservedSpace; Chris@0: Chris@0: // Don't do anything if our widths fit inside the available widths. Chris@0: if ($result->totalWidth() <= $remainingAvailable) { Chris@0: return $result; Chris@0: } Chris@0: Chris@0: // Shrink the minimum widths if the table is too compressed. Chris@0: return $result->distribute($remainingAvailable); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Return proportional weights Chris@0: */ Chris@0: public function distribute($availableWidth) Chris@0: { Chris@0: $result = []; Chris@0: $totalWidth = $this->totalWidth(); Chris@0: $lastColumn = $this->lastColumn(); Chris@0: $widths = $this->widths(); Chris@0: Chris@0: // Take off the last column, and calculate proportional weights Chris@0: // for the first N-1 columns. Chris@0: array_pop($widths); Chris@0: foreach ($widths as $key => $width) { Chris@0: $result[$key] = round(($width / $totalWidth) * $availableWidth); Chris@0: } Chris@0: Chris@0: // Give the last column the rest of the available width Chris@0: $usedWidth = $this->sumWidth($result); Chris@0: $result[$lastColumn] = $availableWidth - $usedWidth; Chris@0: Chris@0: return new ColumnWidths($result); Chris@0: } Chris@0: Chris@0: public function lastColumn() Chris@0: { Chris@0: $keys = $this->keys(); Chris@0: return array_pop($keys); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Return the number of columns. Chris@0: */ Chris@0: public function count() Chris@0: { Chris@0: return count($this->widths); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Calculate how much space is available on average for all columns. Chris@0: */ Chris@0: public function averageWidth($availableWidth) Chris@0: { Chris@0: if ($this->isEmpty()) { Chris@0: debug_print_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); Chris@0: } Chris@0: return $availableWidth / $this->count(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Return the available keys (column identifiers) from the calculated Chris@0: * data set. Chris@0: */ Chris@0: public function keys() Chris@0: { Chris@0: return array_keys($this->widths); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Set the length of the specified column. Chris@0: */ Chris@0: public function setWidth($key, $width) Chris@0: { Chris@0: $this->widths[$key] = $width; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Return the length of the specified column. Chris@0: */ Chris@0: public function width($key) Chris@0: { Chris@0: return isset($this->widths[$key]) ? $this->widths[$key] : 0; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Return all of the lengths Chris@0: */ Chris@0: public function widths() Chris@0: { Chris@0: return $this->widths; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Return true if there is no data in this object Chris@0: */ Chris@0: public function isEmpty() Chris@0: { Chris@0: return empty($this->widths); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Return the sum of the lengths of the provided widths. Chris@0: */ Chris@0: public function totalWidth() Chris@0: { Chris@0: return static::sumWidth($this->widths()); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Return the sum of the lengths of the provided widths. Chris@0: */ Chris@0: public static function sumWidth($widths) Chris@0: { Chris@0: return array_reduce( Chris@0: $widths, Chris@0: function ($carry, $item) { Chris@0: return $carry + $item; Chris@0: } Chris@0: ); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Ensure that every item in $widths that has a corresponding entry Chris@0: * in $minimumWidths is as least as large as the minimum value held there. Chris@0: */ Chris@0: public function enforceMinimums($minimumWidths) Chris@0: { Chris@0: $result = []; Chris@0: if ($minimumWidths instanceof ColumnWidths) { Chris@0: $minimumWidths = $minimumWidths->widths(); Chris@0: } Chris@0: $minimumWidths += $this->widths; Chris@0: Chris@0: foreach ($this->widths as $key => $value) { Chris@0: $result[$key] = max($value, $minimumWidths[$key]); Chris@0: } Chris@0: Chris@0: return new ColumnWidths($result); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Remove all of the specified columns from this data structure. Chris@0: */ Chris@0: public function removeColumns($columnKeys) Chris@0: { Chris@0: $widths = $this->widths(); Chris@0: Chris@0: foreach ($columnKeys as $key) { Chris@0: unset($widths[$key]); Chris@0: } Chris@0: Chris@0: return new ColumnWidths($widths); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Select all columns that exist in the provided list of keys. Chris@0: */ Chris@0: public function selectColumns($columnKeys) Chris@0: { Chris@0: $widths = []; Chris@0: Chris@0: foreach ($columnKeys as $key) { Chris@0: if (isset($this->widths[$key])) { Chris@0: $widths[$key] = $this->width($key); Chris@0: } Chris@0: } Chris@0: Chris@0: return new ColumnWidths($widths); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Combine this set of widths with another set, and return Chris@0: * a new set that contains the entries from both. Chris@0: */ Chris@0: public function combine(ColumnWidths $combineWith) Chris@0: { Chris@0: // Danger: array_merge renumbers numeric keys; that must not happen here. Chris@0: $combined = $combineWith->widths(); Chris@0: foreach ($this->widths() as $key => $value) { Chris@0: $combined[$key] = $value; Chris@0: } Chris@0: return new ColumnWidths($combined); Chris@0: } Chris@0: }