Features
PHP Typography is a PHP based solution to greatly improve web typography. It features the following capabilities (including granular control):
- Hyphenation
- Spacing control, including: gluing values to units, widow protection, and forced internal wrapping of long URLs & email addresses.
- Intelligent character replacement, including smart handling of: quote marks ( “foo” ), dashes ( foo – bar ), ellipses ( … ), trademarks ( ™ ), math symbols ( 1024×768 ), fractions ( 12⁄23 ), and ordinal suffixes ( 3rd )
- CSS hooks for styling: ampersands (class “amp”), acronyms (class “caps”), numbers (class “numbers”), initial single quotes (class “quo”), and initial double quotes & guillemets (class “dquo”).
This project merges, builds upon, and generalizes the functionality of wp-Typogrify, wp-Hyphenate and SmartyPants.
Implementation
While complication is added when manipulating the many options, implementing PHP Typography can be as easy as this:
<?php
$html = "raw HTML here... ";
include('path/to/php-typography.php');
$typo = new phpTypography();
$html = $typo->process($html);
echo $html;
?>
PHP Typography is designed for easy integration with plugin architectures of PHP based content management systems. It has already been ported to WordPress and Zikula CMS. We would like to see other developers pick up this code and implement it for other CMSs. Specifically:
- Drupal
- Joomla
- Expression Engine
If you are a developer for these (or any other PHP based CMS), the heavy lifting is done. You just need to hook it into your CMS.
Testimonial
Here is a fine testimonial from Jenny (who also provided some valuable feedback to further improve the project):
I’ve been evaluating PHP Typography for use in a custom CMS and have been very pleased so far…. It was, as you promised, easy to integrate in our framework (ZF) by wrapping it in a simple class implementing Zend_Filter_Interface. I am using PHP Typography after running the text through HTMLPurifier, and the output has been beautiful thus far.
Requirements
PHP Typography has the following requirements:
- the host server must run PHP 5 or later
- text must be encoded UTF-8
- all markup must be valid xHTML, specifically:
- every element must be closed,
- every attribute must have a value enclosed in quotes, and
- tag names and attributes must be lowercase.
Related
It should also be noted, PHP Typography would not have been possible without another tool we have recently released: PHP Parser. PHP Parser is an amazingly powerful resource that allows HTML to be parsed, modified and reconstructed in ways previously not possible.
Your feedback is much appreciated. How can we make this plugin better? Have you developed a plugin using PHP Typography? Email us at info@kingdesk.com


Jeffrey D. King • 11:30 am on Jul. 9, 2009
A commenter on the wp-Typography project notified us a false positive was returned when the wp-Typography project files were scanned by Avira anti-virus. This issue appears to also occur with the PHP-Typography project.
We have confirmed false positives for the
cy.php,en-GB.php,es.php,ga.php,hr.php, andpr.phplanguage files. We have been unable to divine the cause. We have reported the false positives to Avira and provided them with a copy of the files.Avira’s review of the faslse positives may be viewed here.
We have also tested the files in AVG, and are happy to report no false positives with their anti-virus solution.
Beli • 9:48 am on Jul. 22, 2009
From your article I definitely think that PHP typography sounds great. I will download it, try it and will contact you after that.
Webdesign Stuttgart
Stephen Coles • 3:56 pm on Jul. 24, 2009
Does the hyphenation provide an exception for capitalized words? I love the Hyphenate plugin, but protecting proper nouns is essential for many publications, so I can’t use it if I can’t filter capitalized words.
Jeffrey D. King • 5:07 pm on Jul. 27, 2009
@Stephen Coles #24
A new method
set_hyphenate_title_case()was added to version 1.5 to meet your need.Daniel • 4:02 pm on Jul. 29, 2009
The new WP-MU beta plugin is great news. I tried it out, and this time it actually works under MU as well. Or at least, I can change the hyphenation language this time…
:-)
BTW: A terrific feature would be if the default hyphenation language would be the same as the blog language. Even more so on a MU site, when I activate that plugin for all blogs. And how would you deal with a multilanguage blog? Most of the blogs on my MU install are german, some of them english, and a few are multi-language (german, english, french, maybe italian, take your choice — the last three are official languages within Switzerland), where the user can switch to his favorite language on-the-fly.
Thanks a lot for a wonderful plugin!
– Daniel
Dirk Hillbrecht • 3:15 am on Aug. 2, 2009
Hi,
the typography plugin is really great. It just happens to make a mistake with dates written in ISO format, e.g. “2009 – 08-02″. In these, the first hyphen is enlarged, the second is not. The whole date looks quite garbled. Perhaps you can fix that in some new release? Apart from this, the produced output is really nice and improves the “usage experience” of “the average blog” quite considerably. Thank you!
Best regards,
Dirk
Jeffrey D. King • 2:04 pm on Aug. 4, 2009
@Dirk Hillbrecht #5
Thank you for bringing this issue to my attention. It has been corrected in version 1.8
Dirk Hillbrecht • 10:20 pm on Aug. 4, 2009
…just installed 1.8 and date formatting now works marvellous. Thanks again for this great piece of software! Dirk
Priit Pirita • 6:15 am on Nov. 20, 2009
There seems to be bug in style_initial_quotes(). All 3 mb_substr() there are without encoding parameter. Only after adding “UTF-8″ it works.
Bill • 5:25 pm on Dec. 1, 2009
While integrating this with my project (running on Kohana VO3) I ran into some problems. It works great until it finds any tags, classes, or IDs set to ignore. I’m not sure what the problem is, but when I try to add anything that is listed in the settings[“ignoreTags”], settings[“ignoreClasses”], or settings[“ignoreIDs”] arrays of the phpTypography class, I get an error that says the following:
ErrorException [ Notice ]: Undefined index: openPos.
Jeffrey D. King • 9:48 pm on Dec. 1, 2009
@Priit Pirita #8 & Bill # 9,
Both your issues should be resolved in the new version 1.19 release.
Andrew Hutchings • 3:00 pm on Dec. 18, 2009
I found a bug in version 1.19: on line 1950, $encodings is used without being initialized. Otherwise, works great!
Erik Spaan • 12:08 am on Dec. 22, 2009
Hi,
The plugin has been made available as a plugni to the Zikula CMS by Steffen Voß, check out http://community.zikula.org/module-Extensions-display-ot-component-componentid-264.htm for the details.
Jeffrey D. King • 8:41 am on Dec. 22, 2009
@Erik Spann #12
Thanks for pointing this out. I have updated this post with a link.
Jeffrey D. King • 8:42 am on Dec. 22, 2009
@Andrew Hutchings #11
Thank you for pointing this out. It will be addressed in version 1.20.
Ghusse • 3:01 am on Jan. 4, 2010
Hi,
Is it possible to add two replacement possibilities : the strait quote to the curly one in any case; and the punctuation replacement.
The punctuation replacement has its interest with french language, where double punctuation signs (as “;”, “:”, “?” or “!”) must be preceded by an insecable space.
Thanks for your essential wp plugin and this php library.
Jenny • 7:59 am on Jan. 20, 2010
Hi Jeff,
Found a bug in version 1.20. php-typography.php, line 412, $customReplacementChunks is not initialized. From your indentation it looks like most of the function is supposed to be wrapped in the if statement, but you left out the curly brackets.
Jeffrey D. King • 9:28 am on Jan. 20, 2010
@Jenny #16
Yeah, that is some of the ugliest code I have written. I fixed it in version 1.21 about a week ago, but haven’t got to posting it yet. I will try to get it up today.
Jeffrey D. King • 12:23 pm on Jan. 22, 2010
@Jenny #16
Finally got around to posting 1.21. your issue should be resolved.
BWRic • 6:53 am on Mar. 3, 2010
This looks interesting. I’d written my own ‘typography engine’, but this it probably better made than mine. Going to compare features. Keep up the good work.
webdesign stuttgart • 2:09 pm on May. 15, 2010
great tool!!!
Kikidu • 5:11 pm on Jun. 3, 2010
Wow. Just knew PHP Typography as WordPress-Plugin, integrated it in http://www.kikidu.de and it works – fantastic! Thanks for your great efforts!
anon • 8:59 am on Jul. 14, 2010
+1 Ghusse
Does it replace the space in these particular cases* with alt+0160?
http://fr.wikipedia.org/wiki/Espace_ins%C3%A9cable
*”Le code typographique français recommande, contrairement à d’autres langues telles que l’anglais, une espace insécable devant les signes de ponctuation doubles (point-virgule, deux-points, point d’interrogation, point d’exclamation), entre les guillemets et le texte qu’ils renferment, ainsi que comme séparateur des groupes de caractères (séparateur de groupes de 3 chiffres dans les nombres supérieurs au millier, séparateur sans valeur facilitant la lecture des numéros de téléphone ou des numéros et codes d’identification, etc.).”
No-break thin space, known in Unicode as “NARROW NO-BREAK SPACE” (U+202F). This is required for French punctuation (before ?, !, : or ;).
anon • 9:16 am on Jul. 14, 2010
and “espace fine insécable :  ”
Ann Ray • 2:02 pm on Jul. 18, 2010
Love the potential of your tools, and would love them more if you could make them a bit more accessible for those of us who haven’t made it to the 500 level. But whether or not I manage to implement your modules, I truly appreciate your simply offering the code (because proper quotes matter).
The following are notions from my trainer/tech writer side, in case any resonate:
- A clear “What it affects and what it shouldn’t mangle” for those who can’t read PHP like English, don’t necessarily have time to experiment, and get strangely twitchy about unknown pervasive code ;-)
- Examples/recipes/snippets – even without comments something real is so useful vs. trying to grok a module as theory.
- HTML or PDF addition to the current docs for absorbing big pictures from general concepts (hyphenation, fractions), rather than small bites from known terms (set_dewidow).
- Could you add to http://kingdesk.com/projects/php-typography-documentation/ a set of String/Action/etc. links? Or maybe turn it into a table indicating what’s what? Took me a couple minutes to realize there were 2+ destination pages :-)
- In the function reference, a couple instances of the code boxes such as for “set_tags_to_ignore()” seem to crop on print. Right now I think that info is relatively minor, but just wanted to mention for future writing or style updates. (Keep seeing some great wrapped #ed line formats on blogs – must be a plugin or JS framework?)
Matthew • 6:34 pm on Sep. 15, 2010
I was trying to use this with
-moz-column-count&-webkit-column-countand it doesn’t seem to make any difference. Is there a way to make it work with columns? Thanks!Ian • 9:01 pm on Oct. 2, 2010
Thanks for an excellent piece of software! I have a few comments based on my experience of it in WordPress which I’d like to share.
With reference to what I’ve read in “The Oxford Guide to Style” and “The Elements of Typographic Style”, I’ve always understood that there is a choice between using spaced en dashes (e.g., “nearly – but not quite!”) or close-set em dashes (e.g., “nearly — but not quite!”) between words. Most US publishers adopt the latter, most UK publishers the former. I would like to see a choice between these styles in PHP Typography, which currently uses a rather unconvential style – spaced em dashes.
Similarly, with numerical ranges (e.g., “1950 – 1953″), the accepted form is close-set en dashes, but in this instance PHP Typography uses spaced en dashes. Could this also have its own option?
Thanks again.
Ian • 12:10 pm on Oct. 11, 2010
I’ve just realised the software has formatted the examples in my previous comment so the points I’m making aren’t terribly clear, but I hope it still makes sense nevertheless.
MACC • 8:03 am on Apr. 26, 2011
Hello,
Has anyone tried using this plug-in to hyphenate text returned by Flutter? In case anyone has please post how you got it working.
Thank you!
Dmitriy Navrotskyy • 11:42 am on May. 22, 2011
Hi,
is it possible to configure phpTypography to return HTML entities instead of encoded UTF-8 characters?
pmaczka • 1:41 am on Sep. 29, 2011
Thank you very much. You saved me a lot of work. There are some bugs in polish language, but still script works very good.
Quentin • 6:57 am on Nov. 22, 2011
Hey there!
This library is pretty cool, but there’s one thing I noticed: if you end your quote with a digit it is considered as a inch symbol.
eg. This is a “test 23″.
Hope you can fix that!
Oliver • 10:59 am on Jan. 14, 2012
This is nice but it is very slow (in the meaning of uses a lot of cpu). Did you ever thought about add some caching?
Andrey • 3:01 am on Feb. 4, 2012
Here is a Joomla plugin based on PHP-typography: http://extensions.joomla.org/extensions/style-a-design/typography/18823 Now Joomla fans can have nice typography too. Many thanks to KingDesk for the original script.
Ivana Pešić • 1:44 pm on Feb. 21, 2012
I need help with people’s names. I have international website and there are spanish, finnish people whose names have diacritical marks. Those letters just disappear once they leave comment. I am working in wordpress. any help is welcome.
Ivana Pešić • 2:12 pm on Feb. 21, 2012
ok it was font…
Olaf Bieler • 4:51 am on Feb. 28, 2012
Great tool. But I discovered (maybe) one bug with “set_unit_spacing”. Look what is happening here:
…16 s¶or maybe 6¶s is the difference…
or …0,58¶cm diameter vs. 2,4 m¶in length…
See what I want to say? While using single letter SI units (like m, s…) the ¶ (standing for nbsp) is appended but should be inserted. And it’s always fine with digits < 10.
Saymonz • 11:11 pm on Mar. 11, 2012
Hi!
I use phpTypography and I have an issue: in some of my documents (blog related things), my blog engine have Dublin Core metadatas wrapped in CDATA thing (here’s one example: http://pastebin.com/U4cpY8Ca).
phpTypography mess the first characters, transforming the previous example into this: http://pastebin.com/NDgBp3Zp.
Those things are in the middle of the page, not in any tags that I would like to exclude from phpTypography processing (as I try to make a distribuable plugin for the Dotclear blog engine, I can’t just solve the issue on my site by adding a html tag for this purpose and then excluding this tag from processing).
Disabling the CAPS wrapping feature doesn’t help, the > and < are still getting converted.
Any fix for this?
(Sorry for english, not my native language.)
Johan Muiden • 5:51 am on May. 10, 2012
Because the support for the new CSS hyphens: auto functionality is still limeted this is a life-safer, I’m so glad I found this!
Mark • 2:48 am on Jan. 16, 2013
Hi guys i can’t configure the plug-in can you help me?
John • 7:25 am on Jan. 29, 2013
i try to download plugin but don’t start download can help me?
meble • 9:34 am on Jan. 30, 2013
great plugin, thanks for share!
Kevin • 6:01 pm on Feb. 1, 2013
Hi,
Upgraded to a more recent php 5.3 version:
PHP Notice: Array to string conversion in php-typography.php on line 1377
regards
dg3world • 4:16 am on Feb. 12, 2013
php-pagination-class-more-advanced
debug =$debug;
##default Limit
$this->limit =20;
## Set default css classes.
$this->classFirstPage =’first-page’;
$this->classLastPage =’last-page’;
$this->classNumbers =’numbers-page’;
$this->classNextPage =’next-page’;
$this->classPrevPage =’prev-page’;
$this->classCurrentPage =’current-page’;
## Set default link (pages ) texts.
$this->txtFirstPage =’First’;
$this->txtLastPage =’Last’;
$this->txtCurrentPage =’[current] of [last] ‘;
$this->txtNextPage =’Next’;
$this->txtPrevPage =’Previous’;
// Set default url/tag
$this->base_url =’http://’.$_SERVER[“SERVER_NAME”].$_SERVER[“REQUEST_URI”];
$this->page_tag =’p=’;// Set get var for pages: page– , Page etc.{ page-1,Page1}
$this->page =’1′; // current page.
$this->total_items =false;
$this->max_pages =false;
# how many (number) links on page ? ( 1 t/m 10 == 10 )
# set default @ 10.
$this->links_on_page =10;
}
/* =====================================================================================
Setting /Overwriting needed variables.
========================================================================================*/
/*
Overwrite default limit
*/
public function setLimit ( $limit ){
$this->limit =$limit;
}
/*
Set current page
*/
public function setPage ( $page =1 ){
$this->page =$page;
}
/*
Set total items/rows
*/
public function setTotalItems ( $total ){
$this->total_items =$total;
}
/*
Overwrite default base_url
*/
public function setBaseUrl( $url ){
$this->base_url =$url;
}
/*
Overwrite default page tag
e.g. : p= / page– / Page
Example url:
domain/Page1/
domain/?p=1
domain/artitcletitle-page-1/
*/
public function setPageTag( $tag ){
$this->page_tag =$tag;
}
/*
Overwrite default links per page
Default =10;
Number of links , 1 t/m 10 pages = 10
*/
public function setLinksOnPage( $number ){
$this->links_on_page =$number;
}
/*
Set max_pages,
*/
private function setMaxPages (){
if( $this->max_pages )
return false;
$this->max_pages =ceil($this->total_items / $this->limit );
}
/* =====================================================================================
Function creating the actual links
========================================================================================*/
/*
Check needed Vars to Proceed.
*/
private function checkVars(){
if( $this->limit notice(__METHOD__.’ Limit is set below 1 item per/page.’ );
return false;
}
if( $this->total_items notice(__METHOD__.’ Not enough Items/Rows To output pages’ );
return false;
}
if( $this->total_items limit ){
$this->notice(__METHOD__.’ Not enough Items/Rows To output pages’ );
return false;
}
## reset appended classes.
$this->classAppended =false;
return true;
}
/*
Create page Url
*/
private function createUrl( $page ){
$url =’ERROR_RUN_DEBUG’;
## is the baseUrl valid ?
if( !preg_match(‘/www|http/i’,$this->base_url) )
$this->notice(” BASE_URL not valid ! “. $this->base_url);
##page tag present?
if( !$this->page_tag )
$this->notice(” PAGE TAG nog valid ! “);
## current page present?
if( !$page )
$this->notice(__METHOD__.” PAGE not valid ! “);
## GET var or Mod Rewrite Url ( /page1/ ) ?
# rewrite url with needed link page.
if( preg_match(‘/=/’,$this->page_tag ) ){
$url =preg_replace(‘/(\?|&)?’.$this->page_tag.’\d{1,100}/’,”,$this->base_url );
if( preg_match(‘/=/’,$url) )
$url =$url.’&’.$this->page_tag.$page;
else
$url =$url.’?’.$this->page_tag.$page;
}
else{
## mod rewrite pages.
## Not very good , need to rewrite it with a better function.
if( preg_match(‘/’.str_replace(‘/’,’\/’,$this->page_tag).’/i’,$this->base_url ) )
$url =preg_replace(‘/’.str_replace(‘/’,’\/’,$this->page_tag).’\d{1,100}/’,’$1′.$this->page_tag.$page,$this->base_url );
else
$url =$this->base_url.$this-page_tag.page;
}
return $url;
}
/*
Create Href link
*/
private function createLink( $url , $class, $txt ‚$selected ){
$link =’‘;
else
$link .=’class=”‘.$class.’ ‘.$this->classSelected.’” >’;
$link .=$txt;
$link .=’‘;
return $link;
}
/* =====================================================================================
Rendering Pages links.
========================================================================================*/
/*
First Page.
*/
public function renderFirst(){
if( !$this->checkVars() )
return false;
#selected?
if( $this->page ==1 )
$this->classAppended =true;
##create && return link
return $this->createLink( $this->createUrl( 1 ),
$this->classFirstPage,
$this->txtFirstPage,
$this->classAppended );
}
/*
Last Page.
*/
public function renderLast(){
if( !$this->checkVars() )
return false;
## set max pages ( if not already set).
$this->setMaxPages();
#selected ?
if( $this->page == $max_pages )
$this->classAppended =true;
##create && return link
return $this->createLink( $this->createUrl( $this->max_pages ),
$this->classLastPage,
$this->txtLastPage,
$this->classAppended );
}
/*
Next page
*/
public function renderNext(){
if( !$this->checkVars() )
return false;
## set max pages ( if not already set).
$this->setMaxPages();
##create && return link
if ($this->page max_pages )
return $this->createLink( $this->createUrl( $this->page +1 ),
$this->classNextPage,
$this->txtNextPage,
false );
else
return false;
}
/*
Prevous page
*/
public function renderPrev(){
if( !$this->checkVars() )
return false;
##create && return link
if ($this->page > 1 )
return $this->createLink( $this->createUrl( $this->page –1 ),
$this->classPrevPage,
$this->txtPrevPage,
false );
else
return false;
}
/*
Render number pages
*/
public function renderNumbers(){
if( !$this->checkVars() )
return false;
## check if number of links is valid
if ( $this->links_on_page setMaxPages();
## Caculate pages for links.
$split =ceil( $this->links_on_page / 2 );
$start =$this->page – $split;
$end =$this->page + $split;
if( $start $this->max_pages ){
$batch = ceil( $this->page / $this->links_on_page );
$end = $batch * $this->links_on_page;
if( $end > $this->max_pages )
$end =$this->max_pages;
$start =( $end – $this->links_on_page ) +1;
if( $start < 1 )
$start =1;
}
##Loop and create links.
$links =”;
for($i = $start; $i page )
$links .=$this->createLink( $this->createUrl( $i ),
$this->classCurrentPage,
$this->currentName(),
false
);
## create numbers
else
$links .=$this->createLink( $this->createUrl( $i ),
$this->classNumbers,
$i,
false
);
}
if(strlen($links) > 0 )
return $links;
else
return false;
}
/*
Create special Text for current page on number links
example :
current page = 1 && total pages = 10
output = 1 of 10 ( on default $this->txtCurrentPage )
*/
private function currentName (){
$link_name =”;
## set max pages ( if not already set).
$this->setMaxPages();
#replace [current] with current page.
$link_name =preg_replace(‘/\[current\]/i’,$this->page,$this->txtCurrentPage);
#replace [last] with total pages
$link_name =preg_replace(‘/\[last\]/i’,$this->max_pages,$link_name);
return $link_name;
}
/*
Render complete page set.
@param prefix : wrapper around page link elements.
@param suffix : end wrapper tag.
@param numbers : show numbers ?
@param first_last : show first/next ?
*/
public function renderPageSet( $prefix =”, $suffix=”, $numbers=true , $first_last =true ){
if( !$this->checkVars() )
return ”;
$string =$prefix;
# create first button
if( $first_last )
$string .=$this->renderFirst();
# create previous button
$string .=$this->renderPrev();
#create numbers
if( $numbers )
$string .=$this->renderNumbers();
#create next button
$string .=$this->renderNext();
#create last button.
if( $first_last )
$string.=$this->renderLast();
$string.=$suffix;
return $string;
}
/* =====================================================================================
Overwriting Default variables [ classes/ text ] links.
========================================================================================*/
/*
Overwrite defaults css Classes
*/
public function setClassFirstPage( $class ){
$this->classFirstPage =$class;
}
public function setClassLastPage( $class ){
$this->classLastPage =$class;
}
public function setClassNumbers( $class ){
$this->classNumbers =$class;
}
public function setClassNextPage( $class ){
$this->classNextPage =$class;
}
public function setClassPrevPage( $class ){
$this->classPrevPage =$class;
}
public function setClassCurrentPage( $class ){
$this->classCurrentPage =$class;
}
## will be appended to the classes above if selected.
public function setClassSelected( $class ){
$this->classSelected =$class;
}
/*
Overwrite defaults link texts.
*/
public function setTxtFirstPage( $txt ){
$this->txtFirstPage =$txt;
}
public function setTxtLastPage( $txt ){
$this->txtLastPage =$txt;
}
## needs tags [current] && [last]
## Example : 23 of 100 Pages
## @param $txt = [current] of [last] Pages
public function setTxtCurrentPage( $txt ){
if( !preg_match(‘/\[current\]/i’,$txt) )
$this->notice(__METHOD__.’ NEED [current] TAG in text’);
if( !preg_match(‘/\[last\]/i’,$txt) )
$this->notice(__METHOD__.’ NEED [last] TAG in text’);
$this->txtCurrentPage =$txt;
}
public function setTxtNextPage( $txt ){
$this->txtNextPage =$txt;
}
public function setTxtPrevPage( $txt ){
$this->txtPrevPage =$txt;
}
/*
Notice debug.
*/
public function notice ( $msg ){
if( !$this->debug )
return false;
echo ”;
echo $msg;
echo ”;
}
}
?>
gloves for iphone • 9:19 am on Apr. 17, 2013
I hardly drop comments, but i did some searching and wound up here PHP Typography 1.
mind. Could it be simply me or does it seem like a few of the
remarks look like coming from brain dead individuals?
:-P And, if you are writing on other sites, I would like
to follow everything new you have to post. Would you make a list of all of
all your social sites like your Facebook page, twitter
feed, or linkedin profile?