Zend PDF wysiwyg editor output - php

I'm currently building a PDF editor. I have a problemen with implementing the processing of the tags.
I want to allow the following tags:
[h1],[h2],[h3],[h4],[h4],[h5],[h6],[strong]
I've build an class with a method called drawText(code below).
The [h1] tag will change the font size and the font weight. As you can see in the code I'm outputting lines of text.
Example text line:
This is your [strong]boarding pass[/strong], please save this PDF file on your smartphone or tablet and [strong]show it at the gate[/strong].
I'd like to make the text between the [strong] bold. To do this with Zend_PDF I need to set the TTF file with the bold text and then find the current X-coordinate and call $this->pdf()->drawText(text, X-coordinate, Y-coordinate, charset). I've been thinking and trying for hours to write the code which makes this possible(tried using explode, preg_match_all, etc), but I can't get it to work...
I believe I'm not the only one with this problem, and I hope someone has thought about this and can help a little by telling how he or she did it...
Hope to hear from someone and thanks in advance!
/**
* drawSplittedText()
*
* #param array $text
* #return object Application_Plugin_PdfPlugin
*/
public function drawSplittedText(Array $text)
{
// Count the number of rows.
$textRowCount = count($text);
$i = 0;
foreach ($text as $row)
{
// Replace tabs, because they're not outputted properly.
$row = str_replace("\t", ' ', $row);
// If the character encoding of the currrent row not is UTF-8, convert the row characters to UTF-8.
if (($rowEncoding = mb_detect_encoding($row)) != 'UTF-8') {
$row = iconv($rowEncoding, 'UTF-8', $row);
}
// Output row on PDF
$this->pdf()->drawText($row, $this->_defaultMarginleft, $this->currentY, 'UTF-8');
$this->newLine();
++$i;
}
return $this;
}

The code above is probably where most people start when rendering text with Zend_Pdf, but unfortunately you are going to have to develop something a litte more complex to achieve your goals.
Firstly, you are going to need to keep track of the current x and y location, along with the current font type and size.
Then you'll need a helper function/method to calculate how much space a chunk of text is going to need when rendered in the current font and size.
I would then suggest breaking up your rendering code as follows:
function writeParagraph( $text )
{
// Looks for the next tag and sends all text before that tag to the
// writeText() function. When it gets to a tag it changes the current
// font/size accordingly, then continues sending text until it runs out
// of text or reaches another tag. If you need to deal with nested tags
// then this function may have to call itself recursively.
}
function writeText( $text )
{
// The first thing this function needs to do is call getStringWidth() to
// determine the width of the text that it is being asked to render and if
// the line is too long, shorten it. In practice, a better approach is to
// split the words into an array based on the space between each word and
// then use a while() loop to start building the string to be rendered
// (start with first word, then add second word, then add third word, etc),
// in each iteration testing the length of the current string concatenated
// with the next word to see if the resulting string will still fit. If you
// end up with a situation where adding the next word to the current string
// will result in a string that is too long, render the current string and
// a line feed then start the process again, using the next word as the first
// word in the new string. You will probably want to write a bonus line feed
// at the end as well (unless, of course, you just wrote one!).
}
function getStringWidth( $str )
{
// This needs to return the width of $str
}
I have a sample class (https://github.com/jamesggordon/Wrap_Pdf) that implements the writeText() and getStringWidth() functions/methods, plus includes all of the other stuff, like current location, current style, etc. If you can't figure out the code for the writeParagraph() function let me know and I'll include it in Wrap_Pdf.

Related

how to randomly insert html inside of html string without causing invalid html

I've written a content generator tool for a project im working to assist me batch importing fake content into text fields of a database. It just assists making the site look populated.
I'm using an external class called lorem-php-sum to actually generate the strings that I am inserting. Its incredibly simple really, it just inserts paragraphs of text wrapped in <p> tags (and a random number of them each time) and I then insert these strings into my chosen table within a big loop.
Now the thing is, I want to slightly advance what content is being randomly generated and to add some html list tags, horizontal line tags and other stuff. I want my new html elements to be placed randomly within the paragraphs that I get returned from this paragraph generator class.
The problem is that whilst I can easily insert list tags into my big paragraph string at some random point, I fear sometimes it may insert my new html tags within the existing markup in a way that will break the html.
Does anyone have a trick for inserting html with some rules into another string? I imagine that maybe the php domDocument class can assist with this but not sure now?
You'd need to incorporate some kind of state machine in your generator.
You can think of something like this:
Step1: Choose which element to render: a textnode, a paragraph, a list node.
When you pick a textnode you randomly generate some text and return to Step 1.
When you pick a paragraph you emit <p> and generate some text, emit </p> and return to Step 1.
In the case of a list node you can only make list elements <li>, so pick a random number of elements and fill them with same rules from Step 1.
--
You can also allow nesting. In <li> you can add <strong> and <em>, similar for <p>.
You can make it as crazy as you want I guess :)
Tweak a bit with the coefficients to get good results. Try to make a generator that produces random, but predictable output, total length might be a good thing to control on.
You could hierarchically loop through multidimensional arrays. No cell without a row, no row without a table, as such no li without a ul.
$tags = array("<table>%s</table>\n" ,
array (" <tr>%s</tr>\n" ,
array(" <td>%s</td>\n)),
"<ul>%s</ul>\n",
arrray (" <li>%s</li>\n") //continue with more tags
);
$tags_simple = array("%s", "<strong>%s</strong>",
"<i>%s</i>", "<p>%s</p>\n", "%s</ br>\n"
); //etc, "%s" for a none tag, add more if you like
Pick a ramdom from $tags, multiloop them, sprintf the random sentences and add random simple tags to them. It's a standalone possibility.
So I managed to work this out with other code samples and using domDocument.
I ended up making a function that explodes the string via paragraph tags and returns it as an array containing each paragraph as a separate item.
function splitTextByPara($string,$split_on="p"){
// Add alternative tags to split on with syntax: |//ul|//br
$dom = new DOMDocument();
$dom->loadHTML($string);
$domx = new DOMXPath($dom);
$entries = $domx->evaluate("//".$split_on);
$result = array();
foreach ($entries as $entry) {
$result[] = $entry->ownerDocument->saveHTML( $entry );
}
// re-encode to utf8
$result = array_map("utf8_decode", $result);
return $result;
}

PHP/MySQL Limit Characters

Below is a preview of what I am dealing with:
The heading, the text, and the image is all dynamically created based on the user. They can choose if they want to add a picture, and they can choose what text to put in the heading and the main content.
There cannot be any scrolling, so it has to be visible and cannot go past the bottom of that white.
My question is... how can I limit the text of the body?
If the heading is large and goes down to two lines, and there is a picture, then in order to stay in the lines and not go past the white it has to be limited to a certain amount of characters.
But another user could decide to not have a picture and keep the heading in one line, so that user will have more text to write so the limit should be different.
I guess the confusing part for me is like.. what if a user has no picture and has a short heading, and creates some really long text to fit the size, but then later on decides to add an image.. then that long text will now no longer fit. So now.. I can't limit the text because it's already there.
I hope that makes sense. If anyone could help me through this and give me some ideas I would really appreciate it.
Use this for whatever the user input is for adding text. It could limit the characters they use just change the 250 value.
<script>
function countChar(val){
var len = val.value.length;
if (len >= 250) {
val.value = val.value.substring(0, 250);
}else {
$('#charNum').text(250 - len);
}
};
</script
<textarea id="field" name="description" onKeyUp="countChar(this)"></textarea>
its not only the text you need to limit but also the image thumbnail so that affect the style of your webpage
To limit the number of string .. i would you use a script form here php trim a string
function trim_word($str, $length, $suffix = '...')
{
$len = strlen($str);
if ($len < $length) return $str;
$pattern = sprintf('/^(.{%d,}?)\b.*$/', $length);
$str = preg_replace($pattern, '$1', $str);
$str = trim($str);
$str .= $suffix;
return $str;
}
it trim the text and makes sure it always ends with a word ...
You can use http://phpthumb.sourceforge.net/ to generate thumbnail of fixed size all you need to do it set your desired height and width
There are a few different options to consider. You may want to limit the amount of text the user can enter for starters, to ensure it doesn't overflow.
One thing I would probably do is find the maximum amount of characters you're comfortable with on the page, and use substr on the output from the database to ensure that it never displays more.
http://php.net/manual/en/function.substr.php
You could have a "more" link that way the visitor could read more if they want, but it doesn't break the layout. I would use basic if statements for the logic (if picture exists, trim text to this, if not etc).
Hope that helps.
Surround the entire thing with a container div of the fixed height which you desire with no padding, and inside that place another inner div with no margin and no fixed height, then as the user changes the content create a javascript function to check if the inner div is < the container div; and if not then do not allow the user to make that change - this way you are attacking the problem directly.
<script>
function checkDivs() {
if(inner.style.height >= container.style.height) {
//prevent change
}
}
</script>
<div id='container'>
<div id='inner'>
//User-defined content
</div>
</div>
This function would be attached to whatever GUI the user would edit the content through, as for how to prevent the change, I'd have to know more about your program.

String replace the contents of a div

What I want to do:
I have a div with an id. Whenever ">" occurs I want to replace it with ">>". I also want to prefix the div with "You are here: ".
Example:
<div id="bbp-breadcrumb">Home > About > Contact</div>
Context:
My div contains breadcrumb links for bbPress but I'm trying to match its format to a site-wode bread crumb plugin that I'm using for WordPress. The div is called as function in PHP and outputted as HTML.
My question:
Do I use PHP of Javascript to replace the symbols and how do I go about calling the contents of the div in the first place?
Find the code that's generating the <, and either set the appropriate option (breadcrumb_separator or so) or modify the php code to change the separator.
Modifying supposedly static text with JavaScript is not only a maintenance nightmare, extremely brittle, and might lead to a strange rendering (as users see your site being modified if their system is slow), but will also not work in browsers without (or with disabled) JavaScript support.
You could use CSS to add the you are here text:
#bbp-breadcrumb:before {
content: "You are here: ";
}
Browser support:
http://www.quirksmode.org/css/beforeafter_content.html
You could change the > to >> with javascript:
var htmlElement = document.getElementById('bbp-breadcrumb');
htmlElement.innerHTML = htmlElement.innerHTML.split('>').join('>>').split('>').join('>>')
I don't recommend altering content like this, this is really hacky. You'd better change the ouput rendering of the breadcrumb plugin if possible. Within Wordpress this should be doable.
you can use a regex to match the breadcrumb content.. make the changes on it.. and put it back in the context..
check if this helps you:
$the_existing_html = 'somethis before<div id="bbp-breadcrumb">Home > About > Contact</div>something after'; // let's say this is your curreny html.. just added some context
echo $the_existing_html, '<hr />'; // output.. so that you can see the difference at the end
$pattern ='|<div(.*)bbp-breadcrumb(.*)>(.*)<\/div>|sU'; // find some text that is in a div that has "bbp-breadcrumb" somewhere in its atributes list
$all = preg_match_all($pattern, $the_existing_html, $matches); // match that pattern
$current_bc = $matches[3][0]; // get the text inside that div
$new_bc = 'You are here: ' . str_replace('>', '>>', $current_bc);// replace entity for > with the same thing repeated twice
$the_final_html = str_replace($current_bc, $new_bc, $the_existing_html); // replace the initial breadcrumb with the new one
echo $the_final_html; // output to see where we got

What does the beginning of this HTML optimization code do?

The difficult part is trying to figure out what the stripwhitespace() function does. stripbuffer() is fairly straightforward, but I've been staring at this little piece of code for a while now trying to decipher it, but to no avail. The cryptic variable names and lack of comments don't help much, either. I also had to remove some hyperlinks from the credits because of the spam prevention on this site
<?php
/* ---------------------------------
26 January, 2008 - 2:55pm:
The example below is adapted from a post by londrum 8:29 pm on June 7, 2007:
"crunch up your HTML into a single line
a handy little script..."
This PHP code goes at the very TOP of the PHP-enabled HTML webpage
above EVERYTHING else. Recommendation: use a PHP include file for this
to have only one file to maintain.
--------------------------------- */
function stripwhitespace($bff){
$pzcr=0;
$pzed=strlen($bff)-1;
$rst="";
while($pzcr<$pzed){
$t_poz_start=stripos($bff,"<textarea",$pzcr);
if($t_poz_start===false){
$bffstp=substr($bff,$pzcr);
$temp=stripBuffer($bffstp);
$rst.=$temp;
$pzcr=$pzed;
}
else{
$bffstp=substr($bff,$pzcr,$t_poz_start-$pzcr);
$temp=stripBuffer($bffstp);
$rst.=$temp;
$t_poz_end=stripos($bff,"</textarea>",$t_poz_start);
$temp=substr($bff,$t_poz_start,$t_poz_end-$t_poz_start);
$rst.=$temp;
$pzcr=$t_poz_end;
}
}
return $rst;
}
function stripBuffer($bff){
/* carriage returns, new lines */
$bff=str_replace(array("\r\r\r","\r\r","\r\n","\n\r","\n\n\n","\n\n"),"\n",$bff);
/* tabs */
$bff=str_replace(array("\t\t\t","\t\t","\t\n","\n\t"),"\t",$bff);
/* opening HTML tags */
$bff=str_replace(array(">\r<a",">\r <a",">\r\r <a","> \r<a",">\n<a","> \n<a","> \n<a",">\n\n <a"),"><a",$bff);
$bff=str_replace(array(">\r<b",">\n<b"),"><b",$bff);
$bff=str_replace(array(">\r<d",">\n<d","> \n<d",">\n <d",">\r <d",">\n\n<d"),"><d",$bff);
$bff=str_replace(array(">\r<f",">\n<f",">\n <f"),"><f",$bff);
$bff=str_replace(array(">\r<h",">\n<h",">\t<h","> \n\n<h"),"><h",$bff);
$bff=str_replace(array(">\r<i",">\n<i",">\n <i"),"><i",$bff);
$bff=str_replace(array(">\r<i",">\n<i"),"><i",$bff);
$bff=str_replace(array(">\r<l","> \r<l",">\n<l","> \n<l","> \n<l","/>\n<l","/>\r<l"),"><l",$bff);
$bff=str_replace(array(">\t<l",">\t\t<l"),"><l",$bff);
$bff=str_replace(array(">\r<m",">\n<m"),"><m",$bff);
$bff=str_replace(array(">\r<n",">\n<n"),"><n",$bff);
$bff=str_replace(array(">\r<p",">\n<p",">\n\n<p","> \n<p","> \n <p"),"><p",$bff);
$bff=str_replace(array(">\r<s",">\n<s"),"><s",$bff);
$bff=str_replace(array(">\r<t",">\n<t"),"><t",$bff);
/* closing HTML tags */
$bff=str_replace(array(">\r</a",">\n</a"),"></a",$bff);
$bff=str_replace(array(">\r</b",">\n</b"),"></b",$bff);
$bff=str_replace(array(">\r</u",">\n</u"),"></u",$bff);
$bff=str_replace(array(">\r</d",">\n</d",">\n </d"),"></d",$bff);
$bff=str_replace(array(">\r</f",">\n</f"),"></f",$bff);
$bff=str_replace(array(">\r</l",">\n</l"),"></l",$bff);
$bff=str_replace(array(">\r</n",">\n</n"),"></n",$bff);
$bff=str_replace(array(">\r</p",">\n</p"),"></p",$bff);
$bff=str_replace(array(">\r</s",">\n</s"),"></s",$bff);
/* other */
$bff=str_replace(array(">\r<!",">\n<!"),"><!",$bff);
$bff=str_replace(array("\n<div")," <div",$bff);
$bff=str_replace(array(">\r\r \r<"),"><",$bff);
$bff=str_replace(array("> \n \n <"),"><",$bff);
$bff=str_replace(array(">\r</h",">\n</h"),"></h",$bff);
$bff=str_replace(array("\r<u","\n<u"),"<u",$bff);
$bff=str_replace(array("/>\r","/>\n","/>\t"),"/>",$bff);
$bff=ereg_replace(" {2,}",' ',$bff);
$bff=ereg_replace(" {3,}",' ',$bff);
$bff=str_replace("> <","><",$bff);
$bff=str_replace(" <","<",$bff);
/* non-breaking spaces */
$bff=str_replace(" "," ",$bff);
$bff=str_replace(" "," ",$bff);
/* Example of EXCEPTIONS where I want the space to remain
between two form buttons at */
/* <!-- http://websitetips.com/articles/copy/loremgenerator/ --> */
/* name="select" /> <input */
$bff=str_replace(array("name=\"select\" /><input"),"name=\"select\" /> <input",$bff);
return $bff;
}
ob_start("stripwhitespace");
?>
It looks to me as if it crunches everything before the textarea and after the textarea but it leaves the contents of a textarea alone.
While this code may be somewhat interesting, PHP is notoriously bad at fast string manipulation and all those str_replace calls are a bad, bad idea.
I predict you'd get better performance by using gzip/deflate on the web server to compress the script output before sending.
It's definitely a mess, but it seems as if it strips unnecessary white-space from a string, except from within textareas.
It's obvious what stripBuffer does: it tries to strip all whitespace from its input.
stripwhitespace works as follows:
function stripwhitespace($input){
$currentPosition=0; // start from the first char
$endPosition=strlen($input)-1; // where to stop
$returnValue="";
// while there is more input to process
while($currentPosition<$endPosition){
// find start of next <textarea> tag
$startOfNextTextarea=stripos($input,"<textarea",$currentPosition);
if($startOfNextTextarea===false){
// no textarea tag remaining:
// strip ws from remaining input, append to $returnValue and done!
$bufferToStrip=substr($input,$currentPosition);
$temp=stripBuffer($bufferToStrip);
$returnValue.=$temp;
$currentPosition=$endPosition; // to cause the function to return
}
else{
// <textarea> found
// strip ws from input in the range [current_position, start_of_textarea)
$bufferToStrip=substr($input,$currentPosition,$startOfNextTextarea-$currentPosition);
// append to return value
$temp=stripBuffer($bufferToStrip);
$returnValue.=$temp;
$endOfNextTextarea=stripos($input,"</textarea>",$startOfNextTextarea);
// get contents of <textarea>, append to return value without stripping ws
$temp=substr($input,$startOfNextTextarea,$endOfNextTextarea-$startOfNextTextarea);
$returnValue.=$temp;
// continue looking for textareas after the end of this one
$currentPosition=$endOfNextTextarea;
}
}
return $returnValue;
}
I admit this would be quite harder if you can't "intuitively" tell what it's trying to do, given the special treatment the content of <textarea> tags gets in HTML.
In pseudo code (ish)
bff is the initial buffer
pzcr is the current start
pzed is the current end
rst will have the filtered text appended to it.
while the current start is before the end
t_pos_start is first position of the textarea (after current start)
if there is no text area found
bffstp becomes the substring of the buffer starting at pzcr
temp is buffer stripped.
append temp to rst
set the current start to the current end.
else
set bffstp to the substr between the start and the start of the textarea tag
temp is buffer stripped.
append temp to rst
skip the textarea
temp will be the substr from the start of the text area to the closing text area tag.
append temp (unfiltered) to rst.
set the next start to the end of the textarea (at the start of its closing tag).
end the if
end the while
return the appended buffer (rst)
Hmm - As an html compressor, this code itself is actually bloated as well as hard to read. Regular expressions, used well, should be able to make a much better job of this.

markdown: render linebreaks within block elements as <br>

I know this has been asked (Python Markdown nl2br extension, etc) but none of those answers is doing it for me.
I would like to render markdown so that linebreaks occuring within a <p> element will be rendered as <br>. Example: they type
Here is line one.
And line two.
New paragraph.
should render as
<p>Here is line one.<br>And line two.</p>
<p>New paragraph.</p>
I know that if you want that, you should type two spaces at the end of the line you want to <br>. I am trying to make it so my users don't have to do that, but rather, enter text as though they were using a typewriter (for those who know what that is). One hard return, new line; two hard returns, new paragraph.
I've been working with https://parsedown.org/ and have also experimented with https://commonmark.thephpleague.com; also the Python markdown module with nl2br extension (tried their example verbatim, did not work for me). Whatever I do, I end up with either too many or not enough linebreaks, depending.
I have tried what I thought would be clever and elegant: style my markdown's <p> with white-space: "pre" (also tried pre-line). That works, unless the user has done it "right" with two spaces, in which case you get the unwanted double <br> effect.
Also tried nl2br($markdown) with likewise unreliable results.
I want non-technical users to be able to use some basic formatting as easily as possible, and markdown seems just the thing, but for this detail. I don't want to write a CMS just to work around this. For example, I've thought of adding a boolean markdown property on the entity and letting them choose, yadda yadda... don't wanna go there. I've thought of doing some string-replacement or regexp magic, either at database-write time or just before rendering. But again, hoping to avoid getting too complicated. (To make it a little more challenging, I will also have to import a few thousand legacy records that are non-markdown, and potentially deal with issues around old ones versus new.)
Maybe I'm overlooking a simple, sane way out. Any thoughts as to the best strategy?
Update: by popular demand, code examples of what does not work. It's a Zend MVC application that involves Doctrine entities I call MOTD and MOTW (Message Of The Day and Message Of The Week, respectively); these have a string property called content. Generically I think of these entities as Notes and they implement a NoteInterface. When I retrieve these from the database (via a NotesService class that internally uses a custom Doctrine repository class), it's time to render the content as markdown before the controller assigns it to the view:
// from NotesService.php
use Parsedown;
// stuff omitted...
/**
* gets MOT(D|W) by date
*
* #param DateTime $date
* #param string $type
* #param boolean $render_markdown
* #return NoteInterface|null
*/
public function getNoteByDate(DateTime $date, string $type, bool $render_markdown = true) :? NoteInterface
{
$entity = $this->getRepository()->findByDate($date,$type);
if ($entity && $render_markdown) {
$content = $entity->getContent();
$entity->setContent($this->parsedown($content));
}
return $entity;
}
The point of the boolean $render_markdown is for when we want raw markdown, i.e., when it's going to populate a textarea element of a form.
And the parsedown() method, quite simply:
public function parsedown(string $content) : string
{
if (! $this->parseDown) {
$this->parseDown = new Parsedown();
}
// nope...
// return nl2br($this->parseDown->text($content));
return $this->parseDown->text($content);
}
Inside a viewscript, I just go, e.g.,
if ($this->notes['motd']):
// echo nl2br($this->notes['motd']->getContent());
echo $this->notes['motd']->getContent();
else:
?><p class="font-italic no-note">no MOTD for this date</p><?php
endif;
Now, if in the editing form they input this as content:
here is a line
and here is another
now, new paragraph.
and then we save it in the database, when you select it back out and run it through $parsedown->text($content), you get this HTML:
<p>here is a line
and here is another</p>
<p>now, new paragraph.</p>
Please note, the example input above does not have any space characters preceding the linebreaks. When you do type two spaces before the linebreaks, yeah, it works great. But I don't think my users want to think about that. So using nl2br() helps, except when it results in too many consecutive <br>s in the HTML.
My latest thinking is, use a CSS solution and an input filter that strips <space><space> at the end of lines. When it works, I'll add the story to my memoir. :-)
There may be some more desirable way to achieve this, but finally I decided to
(1) filter the input (at create|update time) with regexp pattern substition to remove trailing ' ' (two consecutive space characters) from lines. I happen to be using ZendFramework's Zend\Filter\PregReplace but it's a de facto wrapper for preg_replace('/( {2,})(\R)/m',$2).
(2) Use CSS to make newlines act like <br> when I display these entities, e.g.,
#motd .card-body p { white-space: pre-line }
Seems to be working for me.

Categories