Skip to content

Commit

Permalink
Added text measurement using metrics.
Browse files Browse the repository at this point in the history
  • Loading branch information
goat1000 committed Jul 23, 2018
1 parent 9e051bf commit 80ae332
Show file tree
Hide file tree
Showing 35 changed files with 1,421 additions and 245 deletions.
4 changes: 4 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
Version 2.28 (23/07/2018)
------------
- Added text measurement class using font metrics and/or character widths.

Version 2.27 (28/03/2018)
------------
- Added support for multiple Y-axes using dataset_axis option.
Expand Down
2 changes: 1 addition & 1 deletion README.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
SVGGraph Library version 2.27
SVGGraph Library version 2.28
=============================

This library provides PHP classes and functions for easily creating SVG
Expand Down
171 changes: 14 additions & 157 deletions SVGGraph.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@
* For more information, please contact <[email protected]>
*/

define('SVGGRAPH_VERSION', 'SVGGraph 2.27');
define('SVGGRAPH_VERSION', 'SVGGraph 2.28');

require_once 'SVGGraphColours.php';
require_once 'SVGGraphText.php';

class SVGGraph {

Expand All @@ -45,25 +46,6 @@ public function __construct($w, $h, $settings = NULL)
unset($settings['structure']);
$this->settings = $settings;
}

// use mbstring if available, or fall back to 1-byte char strings
if(!function_exists('SVGGraphStrlen')) {
if(extension_loaded('mbstring')) {
function SVGGraphStrlen($s, $e) {
return mb_strlen($s, $e);
}
function SVGGraphSubstr($s, $b, $l, $e) {
return mb_substr($s, $b, $l, $e);
}
} else {
function SVGGraphStrlen($s, $e) {
return strlen($s);
}
function SVGGraphSubstr($s, $b, $l, $e) {
return is_null($l) ? substr($s, $b) : substr($s, $b, $l);
}
}
}
}

public function Values($values)
Expand Down Expand Up @@ -613,8 +595,10 @@ protected function DrawLegendEntry($x, $y, $w, $h, $entry)
*/
protected function DrawTitle()
{
$svg_text = new SVGGraphText($this, $this->graph_title_font);

// graph_title is available for all graph types
if(SVGGraphStrlen($this->graph_title, $this->encoding) <= 0)
if($svg_text->Strlen($this->graph_title) <= 0)
return '';

$pos = $this->graph_title_position;
Expand All @@ -625,7 +609,7 @@ protected function DrawTitle()
'text-anchor' => 'middle',
'fill' => $this->graph_title_colour
);
$lines = $this->CountLines($this->graph_title);
$lines = $svg_text->Lines($this->graph_title);
$title_space = $this->graph_title_font_size * $lines +
$this->graph_title_space;
if($pos != 'top' && $pos != 'bottom' && $pos != 'left' && $pos != 'right')
Expand Down Expand Up @@ -657,7 +641,7 @@ protected function DrawTitle()
$this->{$pad_side} += $title_space;

// the Text function will break it into lines
return $this->Text($this->graph_title, $this->graph_title_font_size,
return $svg_text->Text($this->graph_title, $this->graph_title_font_size,
$text);
}

Expand Down Expand Up @@ -757,143 +741,14 @@ protected function Canvas($id)
return $c_el;
}

/**
* Fits text to a box - text will be bottom-aligned
*/
protected function TextFit($text, $x, $y, $w, $h, $attribs = NULL,
$styles = NULL)
{
$pos = array('onload' => "textFit(evt,$x,$y,$w,$h)");
if(is_array($attribs))
$pos = array_merge($attribs, $pos);
$txt = $this->Element('text', $pos, $styles, $text);

/** Uncomment to see the box
$rect = array('x' => $x, 'y' => $y, 'width' => $w, 'height' => $h,
'fill' => 'none', 'stroke' => 'black');
$txt .= $this->Element('rect', $rect);
**/
$this->AddFunction('textFit');
return $txt;
}

/**
* Returns a text element, with tspans for multiple lines
*/
public function Text($text, $line_spacing, $attribs, $styles = NULL)
{
// strip special characters
$text = htmlspecialchars($text, ENT_COMPAT, $this->encoding);

// put entities back in
$text = preg_replace('/&amp;(amp|#x[a-f0-9]+|#\d+);/', '&$1;', $text);
$group = 'text';
$no_tspan = $this->no_tspan;

if(strpos($text, "\n") === FALSE) {
$content = ($text == '' ? ' ' : $text);
} else {
$lines = explode("\n", $text);
$content = '';
$line_attr = array('x' => $attribs['x']);
if($no_tspan) {
$line_attr['y'] = $attribs['y'];
$line_element = 'text';
$group = 'g';
} else {
$line_attr['dy'] = 0;
$line_element = 'tspan';
}
$count = 1;
foreach($lines as $line) {
// blank tspan elements collapse to nothing, so insert a space
if($line == '')
$line = ' ';

// trim because spaces in text are significant
$content .= trim($this->Element($line_element, $line_attr, NULL, $line));
if($no_tspan) {
$line_attr['y'] = $attribs['y'] + $line_spacing * $count;
} else {
$line_attr['dy'] = $line_spacing;
}
++$count;
}
if($no_tspan)
unset($attribs['x'], $attribs['y']);
}
return $this->Element($group, $attribs, $styles, $content);
}

/**
* Returns [width,height] of text
*/
public static function TextSize($text, $font_size, $font_adjust, $encoding,
$angle = 0, $line_spacing = 0)
{
$height = $font_size;
if(!is_string($text)) {
if(is_numeric($text))
$text = Graph::NumString($text);
else
$text = (string)$text;
} else {
// replace all entities with an underscore (just for measurement)
$text = preg_replace('/&[^;]+;/', '_', $text);
}

if($line_spacing > 0) {
$len = 0;
$lines = explode("\n", $text);
foreach($lines as $l)
if(SVGGraphStrlen($l, $encoding) > $len)
$len = SVGGraphStrlen($l, $encoding);
$height += $line_spacing * (count($lines) - 1);
} else {
$len = SVGGraphStrlen($text, $encoding);
}

$width = $len * $font_size * $font_adjust;
if($angle % 180 != 0) {
if($angle % 90 == 0) {
$w = $height;
$height = $width;
$width = $w;
} else {
$a = deg2rad($angle);
$sa = abs(sin($a));
$ca = abs(cos($a));
$w = $ca * $width + $sa * $height;
$h = $sa * $width + $ca * $height;
$width = $w;
$height = $h;
}
}
return array($width, $height);
}

/**
* Returns the number of lines in a string
*/
public static function CountLines($text)
{
$c = 1;
$pos = 0;
while(($pos = strpos($text, "\n", $pos)) !== FALSE) {
++$c;
++$pos;
}
return $c;
}

/**
* Displays readable (hopefully) error message
*/
protected function ErrorText($error)
{
$text = array('x' => 3, 'y' => $this->height - 3);
$style = array(
'font-family' => 'monospace',
'font-family' => 'Courier New',
'font-size' => '11px',
'font-weight' => 'bold',
);
Expand Down Expand Up @@ -924,7 +779,7 @@ protected function ContrastText($x, $y, $text, $fcolour = 'black',
* Builds an element
*/
public function Element($name, $attribs = NULL, $styles = NULL,
$content = NULL)
$content = NULL, $no_whitespace = FALSE)
{
// these properties require units to work well
$require_units = array('stroke-width' => 1, 'stroke-dashoffset' => 1,
Expand Down Expand Up @@ -963,9 +818,11 @@ public function Element($name, $attribs = NULL, $styles = NULL,
}

if(is_null($content))
$element .= "/>\n";
$element .= "/>";
else
$element .= '>' . $content . '</' . $name . ">\n";
$element .= '>' . $content . '</' . $name . ">";
if(!$no_whitespace)
$element .= "\n";

return $element;
}
Expand Down Expand Up @@ -1547,7 +1404,7 @@ public function Fetch($header = TRUE, $defer_javascript = TRUE)
if($this->show_version) {
$text = array('x' => $this->pad_left, 'y' => $this->height - 3);
$style = array(
'font-family' => 'monospace', 'font-size' => '12px',
'font-family' => 'Courier New', 'font-size' => '12px',
'font-weight' => 'bold',
);
$body .= $this->ContrastText($text['x'], $text['y'], SVGGRAPH_VERSION,
Expand Down
18 changes: 9 additions & 9 deletions SVGGraphDataLabels.php
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,6 @@ protected function GetLabelText($dataset, &$gobject)
return '';

if(is_null($gobject['item'])) {
// convert to string - numbers will confuse TextSize()
$content = (string)$gobject['content'];
} else {
if(is_callable($this->data_label_callback)) {
Expand Down Expand Up @@ -381,8 +380,9 @@ protected function MeasureLabel($content, $dataset, $index, &$gobject)

// get size of text
$font_size = max(4, (float)$style['font_size']);
list($w, $h) = Graph::TextSize($content, $font_size, $style['font_adjust'],
$this->encoding, $style['angle'], $font_size);
$svg_text = new SVGGraphText($this->graph, $style['font'], $style['font_adjust']);
list($w, $h) = $svg_text->Measure($content, $font_size, $style['angle'],
$font_size);

// if this label type uses padding, add it in
$type_info = isset($this->types_info[$style['type']]) ?
Expand Down Expand Up @@ -456,9 +456,7 @@ protected function DrawLabel($content, $label_w, $label_h, $dataset, $index,

list($colour, $back_colour) = $this->GetColours($hpos, $vpos, $style);

// reasonable approximation of the baseline position
$font_size = max(4, (float)$style['font_size']);
$text_baseline = $font_size * 0.85;
$text = array(
'font-family' => $style['font'],
'font-size' => $font_size,
Expand All @@ -476,8 +474,10 @@ protected function DrawLabel($content, $label_w, $label_h, $dataset, $index,
}

// need text size without padding, rotation, etc.
list($tbw, $tbh) = Graph::TextSize($content, $font_size,
$style['font_adjust'], $this->encoding, 0, $font_size);
$svg_text = new SVGGraphText($this->graph, $style['font'],
$style['font_adjust']);
list($tbw, $tbh) = $svg_text->Measure($content, $font_size, 0, $font_size);
$text_baseline = $svg_text->Baseline($font_size);

$text['y'] = $y + ($label_h - $tbh) / 2 + $text_baseline;
if($style['angle'] != 0) {
Expand Down Expand Up @@ -559,9 +559,9 @@ protected function DrawLabel($content, $label_w, $label_h, $dataset, $index,
'stroke-linejoin' => 'round',
);
$t1 = array_merge($outline, $text);
$label_markup .= $this->graph->Text($content, $font_size, $t1);
$label_markup .= $svg_text->Text($content, $font_size, $t1);
}
$label_markup .= $this->graph->Text($content, $font_size, $text);
$label_markup .= $svg_text->Text($content, $font_size, $text);

$group = array();
if(isset($gobject['id']) && !is_null($gobject['id']))
Expand Down
Loading

0 comments on commit 80ae332

Please sign in to comment.