Skip to content

Commit

Permalink
Improve vertical alignment and related adjustments
Browse files Browse the repository at this point in the history
WIP
  • Loading branch information
bsweeney committed Jan 29, 2022
1 parent 271c651 commit 850fb1b
Show file tree
Hide file tree
Showing 2 changed files with 157 additions and 95 deletions.
51 changes: 51 additions & 0 deletions src/FrameDecorator/AbstractFrameDecorator.php
Expand Up @@ -107,6 +107,13 @@ abstract class AbstractFrameDecorator extends Frame
*/
public $is_split = false;

/**
* Cache for the calculation of the outer baseline height
*
* @var Frame
*/
private $_cached_outer_baseline_height;

/**
* Class constructor
*
Expand Down Expand Up @@ -339,6 +346,50 @@ public function get_border_box(): array
return $this->_frame->get_border_box();
}

/**
* Return the height of the baseline for the object
*
* @return float
*/
public function get_outer_baseline_height()
{
if (isset($this->_cached_outer_baseline_height)) {
return $this->_cached_outer_baseline_height;
}

$style = $this->_frame->get_style();
$baseline_height = $this->_dompdf->getCanvas()->get_font_baseline($style->font_family, $style->font_size);

$isInlineBlock = $style->display !== "inline"
&& $style->display !== "-dompdf-list-bullet";

if ($this instanceof \Dompdf\FrameDecorator\Image) {
$baseline_height = $style->length_in_pt([
$this->_frame->get_border_box()['h'],
$style->margin_top
]);
} else if ($isInlineBlock) {
// TODO: this loop is excessive for a block with multiple lines since
// we only need the baseline height of the last line in the block
// (plus the position of the line)
/** @var AbstractFrameDecorator $child */
foreach ($this->get_children() as $child) {
if (!$child->is_in_flow()) {
continue;
}
$child_baseline_height = $child->get_outer_baseline_height();
$y_height = $child->get_position("y") - $this->get_content_box()["y"];
if ($child_baseline_height + $y_height > $baseline_height) {
$baseline_height = $child_baseline_height + $y_height;
}
}
$baseline_height += $style->length_in_pt([$style->margin_top]);
}

return $this->_cached_outer_baseline_height = $baseline_height;
}


function set_id($id)
{
$this->_frame->set_id($id);
Expand Down
201 changes: 106 additions & 95 deletions src/FrameReflower/Block.php
Expand Up @@ -559,9 +559,27 @@ protected function _text_align()
function vertical_align()
{
$fontMetrics = $this->get_dompdf()->getFontMetrics();
$style = $this->_frame->get_style();
$baseline_height = $fontMetrics->getFontBaseline($style->font_family, $style->font_size);

$line_height = $fontMetrics->getFontBaseline($style->font_family, $style->line_height);
$nominal_line_height = $fontMetrics->getFontBaseline($style->font_family, $style->font_size * Style::$default_line_height);
$baseline_height_adjustment = 0;
if ($line_height > $nominal_line_height) {
$baseline_height_adjustment += $line_height - $baseline_height;
}

$line_top_adjustment = 0.0;
$line_bottom_adjustment = 0.0;

foreach ($this->_frame->get_line_boxes() as $line) {
$height = $line->h;
$line->y += $line_top_adjustment + $line_bottom_adjustment;
$line_top_adjustment = 0.0;
$line_bottom_adjustment = 0.0;

$line_baseline_height = $baseline_height;
$top_frames = [];
$bottom_frames = [];

// Move all markers to the top of the line box
foreach ($line->get_list_markers() as $marker) {
Expand All @@ -570,115 +588,108 @@ function vertical_align()
}

foreach ($line->frames_to_align() as $frame) {
$style = $frame->get_style();
$isInlineBlock = $style->display !== "inline"
&& $style->display !== "-dompdf-list-bullet";

$baseline = $fontMetrics->getFontBaseline($style->font_family, $style->font_size);
$y_offset = 0;

//FIXME: The 0.8 ratio applied to the height is arbitrary (used to accommodate descenders?)
if ($isInlineBlock) {
// Workaround: Skip vertical alignment if the frame is the
// only one one the line, excluding empty text frames, which
// may be the result of trailing white space
// FIXME: This special case should be removed once vertical
// alignment is properly fixed
$skip = true;

foreach ($line->get_frames() as $other) {
if ($other !== $frame
&& !($other->is_text_node() && $other->get_node()->nodeValue === "")
) {
$skip = false;
break;
}
}
$frame_baseline_height = $frame->get_outer_baseline_height();
if ($frame_baseline_height > $line_baseline_height) {
$line_baseline_height = $frame_baseline_height;
}
}

if ($skip) {
continue;
}
foreach ($line->frames_to_align() as $frame) {
$frame_style = $frame->get_style();
$frame_baseline_height = $frame->get_outer_baseline_height();
$frame_height = $frame->get_margin_height();

$parent = $frame->get_parent();
if (!$frame->is_inline_level()) {
$align = $frame_style->vertical_align;
} else if ($parent instanceof TableCellFrameDecorator) {
$align = "baseline";
} else {
$align = $parent->get_style()->vertical_align;
}

$marginHeight = $frame->get_margin_height();
$imageHeightDiff = $height * 0.8 - $marginHeight;
$y_offset = 0.0;
if (in_array($align, Style::$vertical_align_keywords, true)) {
switch ($align) {
case "middle":
// Aligns the middle of the element with the baseline plus half the x-height of the parent
$y_offset = $line_baseline_height - ($frame_height / 2.0) - ($fontMetrics->getFontHeight($style->font_family, $style->font_size) / 5.0);
break;

$align = $frame->get_style()->vertical_align;
if (in_array($align, Style::$vertical_align_keywords, true)) {
switch ($align) {
case "middle":
$y_offset = $imageHeightDiff / 2;
break;
case "sub":
// Aligns the baseline of the element with the subscript-baseline of its parent
$y_offset = ($line_baseline_height - $frame_baseline_height) + ($baseline_height * 0.3);
break;

case "sub":
$y_offset = 0.3 * $height + $imageHeightDiff;
break;
case "super":
// Aligns the baseline of the element with the superscript-baseline of its parent
$y_offset = ($line_baseline_height - $frame_baseline_height) - ($baseline_height * 0.5);
break;

case "super":
$y_offset = -0.2 * $height + $imageHeightDiff;
break;
case "text-top":
// Aligns the top of the element with the top of the parent element's font
$y_offset = $line_baseline_height - $style->line_height;
break;

case "text-top": // FIXME: this should be the height of the frame minus the height of the text
$y_offset = $height - $style->line_height;
break;
case "top":
// Aligns the top of the element and its descendants with the top of the entire line
// ... *after* other adjustments :/
$top_frames[] = $frame;
break;

case "top":
break;
case "text-bottom":
// Aligns the bottom of the element with the bottom of the parent element's font
$y_offset = $line->h - $frame_height;
break;

case "text-bottom": // FIXME: align bottom of image with the descender?
case "bottom":
$y_offset = 0.3 * $height + $imageHeightDiff;
break;
case "bottom":
// Aligns the bottom of the element and its descendants with the bottom of the entire line
// ... *after* other adjustments :/
$y_offset = $line->h - $frame_height;
$bottom_frames[] = $frame;
break;

case "baseline":
default:
$y_offset = $imageHeightDiff;
break;
}
} else {
$y_offset = $baseline - (float)$style->length_in_pt($align, $style->font_size) - $marginHeight;
case "baseline":
default:
// Aligns the baseline of the element with the baseline of its parent
$y_offset = $line_baseline_height - $frame_baseline_height;
break;
}
} else if (Helpers::is_percent($align)) {
// Aligns the baseline of the element to the given percentage above the baseline of its parent, with the value being a percentage of the line-height property
$y_offset = $line_baseline_height - $frame_baseline_height - (float)$style->length_in_pt($align, $style->line_height);
} else {
$parent = $frame->get_parent();
if ($parent instanceof TableCellFrameDecorator) {
$align = "baseline";
} else {
$align = $parent->get_style()->vertical_align;
}
if (in_array($align, Style::$vertical_align_keywords, true)) {
switch ($align) {
case "middle":
$y_offset = ($height * 0.8 - $baseline) / 2;
break;

case "sub":
$y_offset = $height * 0.8 - $baseline * 0.5;
break;

case "super":
$y_offset = $height * 0.8 - $baseline * 1.4;
break;

case "text-top":
case "top": // Not strictly accurate, but good enough for now
break;

case "text-bottom":
case "bottom":
$y_offset = $height * 0.8 - $baseline;
break;

case "baseline":
default:
$y_offset = $height * 0.8 - $baseline;
break;
// Aligns the baseline of the element to the given length above the baseline of its parent.
$y_offset = $line_baseline_height - $frame_baseline_height - (float)$frame_style->length_in_pt($align, $style->font_size);
}

$y_offset += $baseline_height_adjustment;

if (!Helpers::lengthEqual($y_offset, 0)) {
$frame->move(0, $y_offset);
if ($frame->get_position("y") < $line->y) {
if ($line->y - $frame->get_position("y") > $line_top_adjustment) {
$line_top_adjustment = $line->y - $frame->get_position("y");
}
} else if ($frame->get_position("y") + $frame_height > $line->y + $line->h) {
if (abs($line->y + $line->h - $frame->get_position("y") - $frame_height) > $line_bottom_adjustment) {
$line_bottom_adjustment = abs($line->y + $line->h - $frame->get_position("y") - $frame_height);
}
} else {
$y_offset = $height * 0.8 - $baseline - (float)$style->length_in_pt($align, $style->font_size);
}
}
}

if ($y_offset !== 0) {
$frame->move(0, $y_offset);
$line->h += $line_top_adjustment + $line_bottom_adjustment;
$style->height += $line_top_adjustment + $line_bottom_adjustment;
if ($line_top_adjustment > 0) {
foreach ($line->get_frames() as $frame) {
if (in_array($frame, $top_frames, true)) {
continue;
} else if (in_array($frame, $bottom_frames, true)) {
$frame->move(0, $line->h - $frame->get_margin_height());
} else {
$frame->move(0, $line_top_adjustment);
}
}
}
}
Expand Down

0 comments on commit 850fb1b

Please sign in to comment.