iT邦幫忙

0

如何計算字串寬度(PHP)

king742171 3 月前5416 瀏覽

如題:如何計算字串"寬"度

是【寬】不是【長】唷~

問題描述:
我有一個字串,例如:
$text = "測試資訊information!";

我要如何取得$text的真實寬度呢?

PS1:使用mb_strlen($text,"utf-8");可以取得長度,會得到16
PS2:因中文字的字體方正特性,所以中文字的字體寬度都是固定的,如果只是純中文字串,我可以預設大約的寬度(例如10px),再利用PS1的方法相乘計算。但是,英文字體的寬都不一樣,例如m與i的寬度就不一致,該怎麼計算呢?
PS3:font-family有個Courier的等寬字體可以使用,但是因為某種原因,無法設定為這個字型...=..=

/images/emoticon/emoticon41.gif/images/emoticon/emoticon41.gif/images/emoticon/emoticon41.gif

0
bizpro
iT邦大師 1 級 ‧ 3 月前
最佳解答

以字母/數字寬度來分類, 英文字型有兩種: 等寬monospaced和比例proportional.不同字型的寬度也可能不同.

如果您使用TTF字型, 也許您可以試試 PHP的函式:
http://www.php.net/imagettfbbox

看更多先前的回應...收起先前的回應...
king742171 iT邦新手 4 級 ‧ 3 月前 檢舉

這個方法我也有查到~
可是就如同我PS3說的..
因為某種原因無法設定字型
那個原因就是我用TCPDF將傳給我的HTML字串轉成PDF檔..
而TCPDF字體的設定是
$pdf->setFont('cid0ct', '', 14);
其中..cid0ct是TCPDF內建字型
檔案是PHP不是ttf檔

bizpro iT邦大師 1 級 ‧ 3 月前 檢舉

那這個呢?
http://stackoverflow.com/questions/25208064/tcpdf-how-to-use-getstringwidth-when-text-is-bold

原始碼:

	/**
	 * Returns the length of a string in user unit. A font must be selected.<br>
	 * @param $s (string) The string whose length is to be computed
	 * @param $fontname (string) Family font. It can be either a name defined by AddFont() or one of the standard families. It is also possible to pass an empty string, in that case, the current family is retained.
	 * @param $fontstyle (string) Font style. Possible values are (case insensitive):<ul><li>empty string: regular</li><li>B: bold</li><li>I: italic</li><li>U: underline</li><li>D: line-through</li><li>O: overline</li></ul> or any combination. The default value is regular.
	 * @param $fontsize (float) Font size in points. The default value is the current size.
	 * @param $getarray (boolean) if true returns an array of characters widths, if false returns the total length.
	 * @return mixed int total string length or array of characted widths
	 * @author Nicola Asuni
	 * @public
	 * @since 1.2
	 */
	public function GetStringWidth($s, $fontname='', $fontstyle='', $fontsize=0, $getarray=false) {
		return $this->GetArrStringWidth(TCPDF_FONTS::utf8Bidi(TCPDF_FONTS::UTF8StringToArray($s, $this->isunicode, $this->CurrentFont), $s, $this->tmprtl, $this->isunicode, $this->CurrentFont), $fontname, $fontstyle, $fontsize, $getarray);
	}
king742171 iT邦新手 4 級 ‧ 3 月前 檢舉

剛剛試完~確認可以使用~
不過還有兩個問題待解決~

  1. 是單位問題,因為我的PDF都是Table組成的,裡面的表格寬度數值設定與GetStringWidth回傳的數值設定不一樣,例如:我其中一個表格width=517,但內容的寬度數值回傳145.8222222....(內容為了測試已故意用寬度最窄的i填滿表格寬了),這個問題我應該還能掌握,只是不知道為什麼會這樣~= =
  2. 是GetStringWidth這個函式因為會被使用很多次,因為我目的是要判斷每個表格內的內容寬度(包含自動換行後的總寬),所以我將它放在function中(因為我表格內容本身就是用function去跑的),但是卻無法執行,放在外層就可以執行,這個我無法掌握,研究中,求解~XD
bizpro iT邦大師 1 級 ‧ 3 月前 檢舉

getStringWidth呼叫getArrStringWidth:

	/**
	 * Returns the string length of an array of chars in user unit or an array of characters widths. A font must be selected.<br>
	 * @param $sa (string) The array of chars whose total length is to be computed
	 * @param $fontname (string) Family font. It can be either a name defined by AddFont() or one of the standard families. It is also possible to pass an empty string, in that case, the current family is retained.
	 * @param $fontstyle (string) Font style. Possible values are (case insensitive):<ul><li>empty string: regular</li><li>B: bold</li><li>I: italic</li><li>U: underline</li><li>D: line through</li><li>O: overline</li></ul> or any combination. The default value is regular.
	 * @param $fontsize (float) Font size in points. The default value is the current size.
	 * @param $getarray (boolean) if true returns an array of characters widths, if false returns the total length.
	 * @return mixed int total string length or array of characted widths
	 * @author Nicola Asuni
	 * @public
	 * @since 2.4.000 (2008-03-06)
	 */
	public function GetArrStringWidth($sa, $fontname='', $fontstyle='', $fontsize=0, $getarray=false) {
		// store current values
		if (!TCPDF_STATIC::empty_string($fontname)) {
			$prev_FontFamily = $this->FontFamily;
			$prev_FontStyle = $this->FontStyle;
			$prev_FontSizePt = $this->FontSizePt;
			$this->SetFont($fontname, $fontstyle, $fontsize, '', 'default', false);
		}
		// convert UTF-8 array to Latin1 if required
		if ($this->isunicode AND (!$this->isUnicodeFont())) {
			$sa = TCPDF_FONTS::UTF8ArrToLatin1Arr($sa);
		}
		$w = 0; // total width
		$wa = array(); // array of characters widths
		foreach ($sa as $ck => $char) {
			// character width
			$cw = $this->GetCharWidth($char, isset($sa[($ck + 1)]));
			$wa[] = $cw;
			$w += $cw;
		}
		// restore previous values
		if (!TCPDF_STATIC::empty_string($fontname)) {
			$this->SetFont($prev_FontFamily, $prev_FontStyle, $prev_FontSizePt, '', 'default', false);
		}
		if ($getarray) {
			return $wa;
		}
		return $w;
	}

其中, $getarray可做為除錯用, 看看個別字元的寬度. 另外, 您可以在此函數中嵌進除錯碼, 看看發生什麼事. 或使用除錯器看看.

getArrStringWidth呼叫getCharWidth

	/**
	 * Returns the length of the char in user unit for the current font considering current stretching and spacing (tracking).
	 * @param $char (int) The char code whose length is to be returned
	 * @param $notlast (boolean) If false ignore the font-spacing.
	 * @return float char width
	 * @author Nicola Asuni
	 * @public
	 * @since 2.4.000 (2008-03-06)
	 */
	public function GetCharWidth($char, $notlast=true) {
		// get raw width
		$chw = $this->getRawCharWidth($char);
		if (($this->font_spacing < 0) OR (($this->font_spacing > 0) AND $notlast)) {
			// increase/decrease font spacing
			$chw += $this->font_spacing;
		}
		if ($this->font_stretching != 100) {
			// fixed stretching mode
			$chw *= ($this->font_stretching / 100);
		}
		return $chw;
	}

建議您追蹤TCPDF的原始碼去檢查怎麼計算各寬度的.

king742171 iT邦新手 4 級 ‧ 3 月前 檢舉

老大!!
我找到function不能執行GetStringWidth的原因了~
原本是這麼做的..

function test($text) {
    $textwidth = $pdf->GetStringWidth('Text Test', 'cid0ct', '', 14);
    //...以下省略...
}

我猜會不會是$pdf的關係,於是我這麼做..

function test($text) {
    $pdf = new MYPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false);
    $textwidth = $pdf->GetStringWidth('Text Test', 'cid0ct', '', 14);
    //...以下省略...
}

雖然我function外層也有同一行在做定義$pdf
但好像到function就必須重新定義一次
不過我有個問題...
因為每個表格都會跑一次function
每次都new一個新的...會不會有問題~
(雖然function執行完後這個內部$pdf會不見..)

bizpro iT邦大師 1 級 ‧ 3 月前 檢舉

這個tctable提供您參考:
https://github.com/voilab/tctable

bizpro iT邦大師 1 級 ‧ 3 月前 檢舉

用$this->$pdf->

function test($text) {
    $textwidth = $this->$pdf->GetStringWidth('Text Test', 'cid0ct', '', 14);
    //...以下省略...
}
king742171 iT邦新手 4 級 ‧ 3 月前 檢舉

解決了~
/images/emoticon/emoticon41.gif

0
海綿寶寶
iT邦超人 1 級 ‧ 3 月前

我覺得這是個很難的問題
但是如果你都可以做出「每個中文字都是10px」這種假設的話
那麼問題就可以簡化成
「解析出每個字元是中文或英文,然後累計該字元的寬度」
程式大略如下
可以的話就改去用

<?php
$string = "中go英od 文 jo交b!錯";

$len = my_strlen($string);
echo ($string . " length = [$len]\n");


function my_strlen($string) {
	$arrLength = array(
		"!" => 5, " " => 7,
		"a" => 8, "b" => 8, "c" => 8,
		"d" => 8, "e" => 8, "f" => 8,
		"g" => 8, "h" => 8, "i" => 8,
		"j" =>  7, "k" => 8, "l" => 8,
		"m" => 8, "n" => 8, "o" => 8,
		"p" => 8, "q" => 8, "r" => 8, "s" => 8,
		"t" => 8, "u" => 8, "v" => 8,
		"w" => 8, "x" => 8, "y" => 8, "z" => 8
	);
	
	$iReturn = 0;
	$smblen = mb_strlen($string, 'UTF-8');
	for ($i = 0; $i < $smblen; $i++) {
	   $char = mb_substr($string, $i, 1, 'UTF-8');
   
	   if (array_key_exists($char, $arrLength)) {
	      $charLen = $arrLength[$char];
	   } else {
	      $charLen = 10;	//中文字寬度
	   }
	   echo ("[$i]=[$char] Len=[$charLen]\n");
	   $iReturn = $iReturn + $charLen;
	}

	return $iReturn;
}
?>
king742171 iT邦新手 4 級 ‧ 3 月前 檢舉

這個方法我有想過~
但因為我要得到寬度的內容很多(至少有40幾個)
而其中有大約10幾個內容會有兩三百個字元(含中文字)
這個方法是讓每個字都去跟陣列配對屬於自己的寬度..
這..loading...應該會很重...
尤其我公司案子是醫院..
醫生處理病人資料要快..
所以如果變慢..應該會被醫生洗臉~XD

目前樓上 bizpro 大大提供的函式最適用
回傳的寬度數值也挺精確的~
不過..我在其下方有留言..還有兩個問題待解決~
一起集思廣益吧~哈哈~XD

海綿寶寶 iT邦超人 1 級 ‧ 3 月前 檢舉

其他的我不敢確定
但我可以確定 TCPDF 做的事比我多得多多了

GetArrStringWidth 呼叫 GetCharWidth
GetCharWidth 呼叫 getRawCharWidth
getRawCharWidth 呼叫 getAbsFontMeasure
getAbsFontMeasure 回傳 measure

如果你認為讓每個字都去跟陣列配對屬於自己的寬度..
會讓程式變慢的話
再讓你看一段 TCPDF 的 source code
你看看他是在做什麼

	/**
	 * Returns the length of the char in user unit for the current font.
	 * @param $char (int) The char code whose length is to be returned
	 * @return float char width
	 * @author Nicola Asuni
	 * @public
	 * @since 5.9.000 (2010-09-28)
	 */
	public function getRawCharWidth($char) {
		if ($char == 173) {
			// SHY character will not be printed
			return (0);
		}
		if (isset($this->CurrentFont['cw'][$char])) {
			$w = $this->CurrentFont['cw'][$char];
		} elseif (isset($this->CurrentFont['dw'])) {
			// default width
			$w = $this->CurrentFont['dw'];
		} elseif (isset($this->CurrentFont['cw'][32])) {
			// default width
			$w = $this->CurrentFont['cw'][32];
		} else {
			$w = 600;
		}
		return $this->getAbsFontMeasure($w);
	}

最後
我只想說
Good luck.

king742171 iT邦新手 4 級 ‧ 3 月前 檢舉

其實後來又回頭看原始碼...
確實做得事情比你的多...
我的錯~抱歉~~
是我想多了~XD
不過~換個角度想~至少我可以不用自己定義每個字元的寬度~
自己定義又不一定準確~
而且~也不一定全部都定義得到~例如一些特殊符號~

最後
我只想說
Thanks for your answer.

0
wwx
iT邦研究生 1 級 ‧ 3 月前

沒在用PHP的我,查了這個 imagefontwidth
http://php.net/manual/en/function.imagefontwidth.php
那麼可以把字串長度中的每個字分別抓出字寬相加
即可得到所說的 字串"寬"度

king742171 iT邦新手 4 級 ‧ 3 月前 檢舉

這個我有查到過~
是一個方法~
但前提是字串要轉成圖片~
如果上一樓大大方法不行~
我再來嘗試這個~^^

我要發表回答

立即登入回答