I'm implementing an AVL Tree (a self-balancing Binary Search Tree) in PHP and have things working pretty normally. I have in-order, pre-order, and level-order iterators working, but I can't figure out how to do a post-order iterator for a BST. Google searches turn up how to do an iterative post-order traversal, but not an iterator.
So far my only success has been to use a post-order traversal to build an array and then return an array iterator. This is bad because it iterates the tree twice and adds more space complexity.
What is the general algorithm for building a post-order iterator?
The only reason the php tag is here is that iterators in PHP are different from the ones in Java or C++. It might affect your advice.
Also, if PHP had generators, this would be a breeze because the post-order traversal could simply yield values, turning it into an iterator . . .
Edit: here is the in-order iterator implementation. Maybe it can help you understand what I want from a post-order iterator:
class InOrderIterator implements Iterator {
/**
* #var ArrayStack
*/
protected $stack;
/**
* #var BinaryNode
*/
protected $root;
/**
* #var BinaryNode
*/
protected $value;
public function __construct(BinaryNode $root) {
$this->stack = new ArrayStack;
$this->root = $root;
}
/**
* #link http://php.net/manual/en/iterator.current.php
* #return mixed
*/
public function current() {
return $this->value->getValue();
}
/**
* #link http://php.net/manual/en/iterator.next.php
* #return void
*/
public function next() {
/**
* #var BinaryNode $node
*/
$node = $this->stack->pop();
$right = $node->getRight();
if ($right !== NULL) {
// left-most branch of the right side
for ($left = $right; $left !== NULL; $left = $left->getLeft()) {
$this->stack->push($left);
}
}
if ($this->stack->isEmpty()) {
$this->value = NULL;
return;
}
$this->value = $this->stack->peek();
}
/**
* #link http://php.net/manual/en/iterator.key.php
* #return NULL
*/
public function key() {
return NULL; //no keys in a tree . . .
}
/**
* #link http://php.net/manual/en/iterator.valid.php
* #return boolean
*/
public function valid() {
return $this->value !== NULL;
}
/**
* #link http://php.net/manual/en/iterator.rewind.php
* #return void
*/
public function rewind() {
$this->stack->clear();
for ($current = $this->root; $current !== NULL; $current = $current->getLeft()) {
$this->stack->push($current);
}
$this->value = $this->stack->peek();
}
}
I was able to ALMOST convert an example of an iterative traversal in C++ into an iterator. Latest on github. Still need to figure out a few cases.
class PostOrderIterator implements Iterator {
/**
* #var ArrayStack
*/
protected $stack;
/**
* #var BinaryNode
*/
protected $root;
/**
* #var BinaryNode
*/
protected $value;
protected $current;
public function __construct(BinaryNode $root) {
$this->stack = new ArrayStack;
$this->root = $root;
}
/**
* #link http://php.net/manual/en/iterator.current.php
* #return mixed
*/
public function current() {
return $this->current->getValue();
}
/**
* #link http://php.net/manual/en/iterator.next.php
* #return void
*/
public function next() {
/**
* #var BinaryNode $node
*/
if ($this->value !== NULL) {
$right = $this->value->getRight();
if ($right !== NULL) {
$this->stack->push($right);
}
$this->stack->push($this->value);
$this->value = $this->value->getLeft();
$this->next();
return;
}
if ($this->stack->isEmpty()) {
$this->current = $this->value;
$this->value = NULL;
return;
}
$this->value = $this->stack->pop();
$right = $this->value->getRight();
if ($right !== NULL && !$this->stack->isEmpty() && ($right === $this->stack->peek())) {
$this->stack->pop();
$this->stack->push($this->value);
$this->value = $this->value->getRight();
$this->current = $this->value;
} else {
if ($this->current === $this->value) {
$this->value = NULL;
$this->next();
} else {
$this->current = $this->value;
$this->value = NULL;
}
}
}
/**
* #link http://php.net/manual/en/iterator.key.php
* #return NULL
*/
public function key() {
return NULL; //no keys in a tree . . .
}
/**
* #link http://php.net/manual/en/iterator.valid.php
* #return boolean
*/
public function valid() {
return $this->current !== NULL;
}
/**
* #link http://php.net/manual/en/iterator.rewind.php
* #return void
*/
public function rewind() {
$this->stack->clear();
$this->value = $this->root;
$this->next();
}
}
I'm not able to describe the algorithm or justify what it is doing, but hopefully over time it will make sense.
Related
I have a Quiz class.
This class load 10 questions from a database depending on the level and the type of the quiz Object:
level 0 load the ten first, level 1 load the next ten and so on.
So in my test i create in a test database 30 questions.
Then i create quiz object with different level and i check that the first question in the quiz steps array match what i expect.
This test "quiz_contain_steps_depending_on_type_and_level()" failed randomly at least once every 5 launches.
This is the QuizTest class
<?php
namespace App\Tests\Quiz;
use App\Quiz\Question;
use App\Quiz\Quiz;
use App\Quiz\QuizQuestionRepositoryManager;
use App\Quiz\QuizStep;
use App\Quiz\QuizType;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Persistence\ObjectRepository;
use Faker\Factory;
use Faker\Generator;
use ReflectionException;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Config\Definition\Exception\Exception;
class QuizTest extends KernelTestCase
{
use QuestionLoremTrait;
use PrivatePropertyValueTestTrait;
private Generator $faker;
private ?EntityManagerInterface $em;
private ObjectRepository $questionRepo;
private QuizQuestionRepositoryManager $quizQuestionManager;
protected function setUp(): void
{
$kernel = self::bootKernel();
$this->faker = Factory::create();
$this->em = $kernel->getContainer()->get('doctrine')->getManager();
$this->em->getConnection()->beginTransaction();
$this->questionRepo = $kernel->getContainer()->get('doctrine')->getRepository(Question::class);
$this->quizQuestionManager = new QuizQuestionRepositoryManager($this->questionRepo);
}
protected function tearDown(): void
{
parent::tearDown();
$this->em->getConnection()->rollBack();
$this->em->close();
$this->em = null;
}
/**
* #test
* #dataProvider provideQuizDataAndFirstQuestionExpectedIndex
* #param array $quizData
* #param int $firstQuestionExpectedIndex
* #throws ReflectionException
* #throws \Exception
*/
public function quiz_contain_steps_depending_on_type_and_level(array $quizData, int $firstQuestionExpectedIndex)
{
//We have questions in db
$questions = [];
for ($q = 1; $q <= 30; $q++) {
$question = $this->persistLoremQuestion($this->faker, $this->em);
$questions[] = $question;
}
$this->em->flush();
//When we create Quiz instance $quiz
$quiz = new Quiz($this->quizQuestionManager,quizData: $quizData);
//When we look at this $quiz steps property
$quizSteps = $quiz->getSteps();
/** #var QuizStep $firstStep */
$firstStep = $quizSteps[0];
//We expect
$this->assertNotEmpty($quizSteps);
$this->assertCount(10, $quizSteps);
//We expect if quiz is type normal and level variable questions depends of level:
$this->assertEquals($firstStep->getQuestion(), $questions[$firstQuestionExpectedIndex]);
}
public function provideQuizDataAndFirstQuestionExpectedIndex(): array
{
return [
[[], 0],
[['type' => QuizType::NORMAL, 'level' => '1'], 10],
[['type' => QuizType::NORMAL, 'level' => '2'], 20]
];
}
}
This is the Trait who generate fake question
<?php
namespace App\Tests\Quiz;
use App\Quiz\Question;
use Doctrine\ORM\EntityManagerInterface;
use Exception;
use Faker\Generator;
Trait QuestionLoremTrait{
/**
* This function persist a aleatory generated question, you must flush after
* #param Generator $faker
* #param EntityManagerInterface $em
* #return Question
* #throws Exception
*/
public function persistLoremQuestion(Generator $faker, EntityManagerInterface $em): Question
{
$nbrOfProps = random_int(2,4);
$answerPosition = random_int(0, $nbrOfProps - 1);
$props = [];
for ($i = 0; $i < $nbrOfProps; $i++){
$props[$i] = $faker->sentence ;
}
$question = new Question();
$question
->setSharedId(random_int(1, 2147483647))
->setInfo($faker->paragraph(3))
->setStatement($faker->sentence ."?")
->setProps($props)
->setAnswerPosition($answerPosition)
;
$em->persist($question);
return $question;
}
}
This is my Quiz class:
<?php
namespace App\Quiz;
use Symfony\Component\Config\Definition\Exception\Exception;
class Quiz
{
/**
* Quiz constructor.
* #param QuizQuestionManagerInterface $quizQuestionManager
* #param array $quizData
* This array of key->value represent quiz properties.
* Valid keys are 'step','level','type'.
* You must use QuizType constant as type value
* #param string $type
* #param int $level
* #param int $currentStep
* #param array $steps
*/
public function __construct(
private QuizQuestionManagerInterface $quizQuestionManager,
private string $type = QuizType::FAST,
private int $level = 0,
private array $quizData = [],
private int $currentStep = 0,
private array $steps = [])
{
if ($quizData != []) {
$this->hydrate($quizData);
}
$this->setSteps();
}
private function hydrate(array $quizData)
{
foreach ($quizData as $key => $value) {
$method = 'set' . ucfirst($key);
// If the matching setter exists
if (method_exists($this, $method) && $method != 'setQuestions') {
// One calls the setter.
$this->$method($value);
}
}
}
public function getCurrentStep(): int
{
return $this->currentStep;
}
public function getLevel(): int
{
return $this->level;
}
public function getType(): string
{
return $this->type;
}
public function getSteps(): array
{
return $this->steps;
}
private function setCurrentStep($value): void
{
$this->currentStep = $value;
}
private function setLevel(int $level): void
{
$this->level = $level;
}
private function setType($type): void
{
if (!QuizType::exist($type)) {
throw new Exception("This quiz type didn't exist, you must use QuizType constante to define type", 400);
}
$this->type = $type;
}
private function setSteps()
{
$this->steps = [];
$questions = $this->quizQuestionManager->getQuestions($this->type, $this->level);
foreach ($questions as $question) {
$this->steps[] = new QuizStep(question: $question);
}
}
}
This is the Question class:
<?php
namespace App\Quiz;
use App\Repository\QuestionRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity(repositoryClass=QuestionRepository::class)
*/
class Question
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private ?int $id;
/**
* #ORM\Column(type="integer")
*/
private ?int $sharedId;
/**
* #ORM\Column(type="string", length=1000, nullable=true)
* #Assert\Length(max=1000)
*/
private ?string $info;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private ?string $statement;
/**
* #ORM\Column(type="array")
*/
private array $props = [];
/**
* #ORM\Column(type="integer")
*/
private ?int $answerPosition;
public function getId(): ?int
{
return $this->id;
}
public function getSharedId(): ?int
{
return $this->sharedId;
}
public function setSharedId(int $sharedId): self
{
$this->sharedId = $sharedId;
return $this;
}
public function getInfo(): ?string
{
return $this->info;
}
public function setInfo(?string $info): self
{
$this->info = $info;
return $this;
}
public function getStatement(): ?string
{
return $this->statement;
}
public function setStatement(?string $statement): self
{
$this->statement = $statement;
return $this;
}
public function getProps(): ?array
{
return $this->props;
}
public function setProps(array $props): self
{
$this->props = $props;
return $this;
}
public function getAnswerPosition(): ?int
{
return $this->answerPosition;
}
public function setAnswerPosition(int $answerPosition): self
{
$this->answerPosition = $answerPosition;
return $this;
}
}
If anyone understands this behavior. I thank him in advance for helping me sleep better :-)
Thanks to #AlessandroChitolina comments.
The set of questions created in my test was not always recorded in the same order by my in my database.
So instead of testing the expected question from my starting $questions array, i retrieve the questions from the database in a new $dbQuestions array. That solve my problème.
This is the new test:
/**
* #test
* #dataProvider provideQuizDataAndFirstQuestionExpectedIndex
* #param array $quizData
* #param int $firstQuestionExpectedIndex
* #throws \Exception
*/
public function quiz_contain_steps_depending_on_type_and_level(array $quizData, int $firstQuestionExpectedIndex)
{
//We have questions in db
$questions = [];
for ($q = 1; $q <= 30; $q++) {
$question = $this->persistLoremQuestion($this->faker, $this->em);
$questions[] = $question;
}
$this->em->flush();
$dbQuestions = $this->questionRepo->findAll();
//When we create Quiz instance $quiz
$quiz = new Quiz($this->quizQuestionManager,quizData: $quizData);
//When we look at this $quiz steps property
$quizSteps = $quiz->getSteps();
/** #var QuizStep $firstStep */
$firstStep = $quizSteps[0];
//We expect
$this->assertNotEmpty($quizSteps);
$this->assertCount(10, $quizSteps);
//We expect if quiz is type normal and level variable questions depends of level:
$this->assertEquals($firstStep->getQuestion(), $dbQuestions[$firstQuestionExpectedIndex]);
}
I have Reservation entity, which contains some ReservationRev(isions) collection. ReservationRev contains PersonStay collection.
In Reservation I have virtual property personStays (getter, add, remove, setter).
When I try to POST Reservation with personStays I get an error - denormalizer for Collection can't be found.
class Reservation
/**
* #var Collection
* #Doctrine\ORM\Mapping\OneToMany(
* targetEntity="Zakjakub\OswisAccommodationBundle\Entity\ReservationRev",
* mappedBy="container",
* cascade={"all"},
* orphanRemoval=true
* )
*/
protected $revisions;
public function __construct(Nameable $nameable, ...) {
$this->revisions = new ArrayCollection();
$this->addRevision(new ReservationRev($nameable));
...
}
final public function addPersonStay(?PersonStay $personStay): void
{
if (!$this->getPersonStays()->contains($personStay)) {
$newRevision = clone $this->getRevisionByDate();
$newRevision->addPersonStay($personStay);
$this->addRevision($newRevision);
}
}
final public function getPersonStays(?\DateTime $dateTime = null): Collection
{
return $this->getRevisionByDate($dateTime)->getPersonStays() ?? new ArrayCollection();
}
final public function removePersonStay(?PersonStay $personStay): void
{
if ($this->getPersonStays()->contains($personStay)) {
$newRevision = clone $this->getRevisionByDate();
$newRevision->removePersonStay($personStay);
$this->addRevision($newRevision);
}
}
final public function setPersonStays(?Collection $personStays): void
{
$newRevision = clone $this->getRevisionByDate();
$newRevision->setPersonStays($personStays ?? new ArrayCollection());
$this->addRevision($newRevision);
}
class ReservationRev
/**
* #var Collection
* #Doctrine\ORM\Mapping\ManyToMany(
* targetEntity="Zakjakub\OswisAccommodationBundle\Entity\PersonStay",
* inversedBy="reservationRevisions",
* cascade={"all"}
* )
* #Doctrine\ORM\Mapping\JoinTable(name="accommodation_reservations_person_stays")
*/
protected $personStays;
/**
* #var Reservation
* #Doctrine\ORM\Mapping\ManyToOne(
* targetEntity="Zakjakub\OswisAccommodationBundle\Entity\Reservation",
* inversedBy="revisions"
* )
* #Doctrine\ORM\Mapping\JoinColumn(name="container_id", referencedColumnName="id")
*/
protected $container;
public function __construct(Nameable $nameable, ...) {
$this->personStays = new ArrayCollection();
...
}
final public function addPersonStay(?PersonStay $personStay): void
{
if ($personStay) {
return;
}
if (!$this->personStays->contains($personStay)) {
$this->personStays->add($personStay);
$personStay->addReservationRevision($this);
}
}
final public function removePersonStay(?PersonStay $personStay): void
{
if ($personStay) {
return;
}
if ($this->personStays->removeElement($personStay)) {
$personStay->removeReservationRevision($this);
}
}
final public function setPersonStays(Collection $personStays): void
{
foreach ($this->personStays as $personStay) {
if (!$personStays->contains($personStay)) {
$this->removePersonStay($personStay);
}
}
foreach ($personStays as $personStay) {
if (!$this->personStays->contains($personStay)) {
$this->addPersonStay($personStay);
}
}
}
/**
* #return Collection
*/
final public function getPersonStays(): Collection
{
return $this->personStays ?? new ArrayCollection();
}
class PersonStay
/**
* #var Person
* #Doctrine\ORM\Mapping\ManyToOne(
* targetEntity="Zakjakub\OswisAccommodationBundle\Entity\Person",
* inversedBy="personStays",
* cascade={"all"}
* )
* #Doctrine\ORM\Mapping\JoinColumn(name="person_id", referencedColumnName="id")
*/
protected $person;
/**
* #var Collection
* #Doctrine\ORM\Mapping\ManyToMany(
* targetEntity="Zakjakub\OswisAccommodationBundle\Entity\ReservationRev",
* mappedBy="personStays"
* )
*/
protected $reservationRevisions;
/**
* #var Room
* #Doctrine\ORM\Mapping\ManyToOne(
* targetEntity="Zakjakub\OswisAccommodationBundle\Entity\Room",
* inversedBy="personStays",
* cascade={"persist"}
* )
* #Doctrine\ORM\Mapping\JoinColumn(name="room_id", referencedColumnName="id")
*/
protected $room;
public function __construct(
?Person $person = null,
?ReservationRev $reservationRevision = null,
?Room $room = null,
...
) {
$this->setPerson($person);
$this->reservationRevisions = new ArrayCollection();
$this->addReservationRevision($reservationRevision);
$this->setRoom($room);
...
}
/**
* #param ReservationRev|null $reservationRev
*/
final public function addReservationRevision(?ReservationRev $reservationRev): void
{
if (!$reservationRev) {
return;
}
if (!$this->reservationRevisions->contains($reservationRev)) {
$this->reservationRevisions->add($reservationRev);
$reservationRev->addPersonStay($this);
}
}
/**
* #return Person
*/
final public function getPerson(): ?Person
{
return $this->person;
}
/**
* Set person.
*
* #param Person|null $person
*/
final public function setPerson(?Person $person): void
{
if (null !== $this->person) {
$this->person->removePersonStay($this);
}
if ($person && $this->person !== $person) {
$person->addPersonStay($this);
$this->person = $person;
}
}
/**
* #return Room|null
*/
final public function getRoom(): ?Room
{
return $this->room;
}
/**
* #param Room|null $room
*/
final public function setRoom(?Room $room): void
{
if (null !== $this->room) {
$this->room->removePersonStay($this);
}
if ($room && $this->room !== $room) {
$room->addPersonStay($this);
$this->room = $room;
}
}
/**
* #return Collection
*/
final public function getReservationRevisions(): Collection
{
return $this->reservationRevisions ?? new ArrayCollection();
}
/**
* #param ReservationRev|null $reservationRev
*/
final public function removeReservationRevision(?ReservationRev $reservationRev): void
{
if (!$reservationRev) {
return;
}
if ($this->reservationRevisions->removeElement($reservationRev)) {
$reservationRev->removePersonStay($this);
}
}
POST
{
"personStays": null (or [] or [<some person stays>]),
"description": "string",
"note": "string"
}
Error
Could not denormalize object of type Doctrine\\Common\\Collections\\Collection, no supporting normalizer found.
Symfony 4.2.2
Api-platform 2.3.6
i have a map stored as a multidimensional array ($map[row][col]) and i'd wish to create a path from point A to point B.
since i can have some obstacles with turns, corners etc etc, i'd wish to use the A* search to calculate the fastest path.
so the general function is
f(x) = g(x) + h(x)
and i have all of these values. g(x) is cost of the move (and it's saved on the map); h(x) is the linear distance between A and B.
so i have everything i need, but i have a question: how can i organize everything?
i have no need to test for alternative paths, since a square on the map can be passable or not, so when i reach the target it should be the shortest one.
how can i organize everything?
i tried with multidimensional array, but i get lost.. :(
EDIT
i worked out some code, it's pretty a wall of text :)
//$start = array(28, 19), $end = array(14, 19)
//$this->map->map is a multidimensional array, everything has a cost of 1, except for
//blocking squares that cost 99
//$this->map->map == $this->radar
//blocking square at 23-17, 22-18, 22-19, 22-20, 23-21, 19-17, 20-18,20-19,20-20,19-21
//they are like 2 specular mustache :P
function createPath($start, $end)
{
$found = false;
$temp = $this->cost($start, $end);
foreach($temp as $t){
if($t['cost'] == $this->map->map[$end[0]][$end[1]]) $found = true;
$this->costStack[$t['cost']][] = array('grid' => $t['grid'], 'dir' => $t['dir']);
}
ksort($this->costStack);
if(!$found) {
foreach($this->costStack as $k => $stack){
foreach($stack as $kn => $node){
$curNode = $node['grid'];
unset($this->costStack[$k][$kn]);
break;
}
if(!count($this->costStack[$k])) unset($this->costStack[$k]);
break;
}
$this->createPath($curNode, $end);
}
}
function cost($current, $target)
{
$return = array();
//$AIM = array('n' => array(-1, 0),'e' => array( 0, 1),'s' => array( 1, 0),'w' => array( 0, -1));
foreach($this->AIM as $direction => $offset){
$position[0] = $current[0] + $offset[0];
$position[1] = $current[1] + $offset[1];
//radar is a copy of the map
if ( $this->radar[$position[0]][$position[1]] == 'V') continue;
else $this->radar[$position[0]][$position[1]] = 'V';
$h = (int) $this->distance($position, $target);
$g = $this->map->map[$position[0]][$position[1]];
$return[] = array('grid' => $position,
'dir' => $direction,
'cost' => $h + $g);
}
return $return;
}
i hope you can understand everything, i tried to be clear as much as possible.
finally i can get to my destination, expanding only cheaper nodes, but now i have a problem.
how can i turn it into directions? i have to store a stack of orders (ie n, n, e etc etc), how can i identify a path inside these values?
My structure was:
Have a Grid-class for holding all possible nodes (propably your array
goes here)
Have a Node-class representing the nodes. Nodes will also calculated costs and store predecessor/g-values set by AStar
Have a AStar class, which will only get two nodes (e.g. startNode, endNode)
Have a PriorityQueue as your open-list
when a Node is asked (by AStar) about it's neighbors, delegated that call to Grid
I'll try to collect some code samples from a prior project, could take a while though.
Update
(found my old project ;))
It's probably not exactly what you're looking for, but maybe it's a start.
So using the files below, and mazes defined like:
00000000000000000000000
00000000000000000000000
0000000000W000000000000
0000000000W000000000000
0000000000W000000000000
0000000000W00000WWWWWWW
0000000000W000000000000
S000000000W00000000000E
(test/maze.txt)
You'll get something like this:
00000000000000000000000
0000000000X000000000000
000000000XWXX0000000000
00000000X0W00X000000000
000000XX00W000X00000000
00000X0000W0000XWWWWWWW
0000X00000W00000XXX0000
SXXX000000W00000000XXXE
index.php
error_reporting(E_ALL ^ E_STRICT);
ini_set('display_errors', 'on');
header('Content-Type: text/plain; charset="utf-8"');
// simple autoloader
function __autoload($className) {
$path = '/lib/' . str_replace('_', '/', $className) . '.php';
foreach (explode(PATH_SEPARATOR, get_include_path()) as $prefix) {
if (file_exists($prefix . $path)) {
require_once $prefix . $path;
}
}
}
// init maze
$maze = new Maze_Reader('test/maze.txt');
$startNode = $maze->getByValue('S', true);
$endNode = $maze->getByValue('E', true);
$astar = new AStar;
if ($astar->getPath($startNode, $endNode)) {
do {
if (!in_array($endNode->value(), array('S', 'E'))) {
$endNode->value('X');
}
} while ($endNode = $endNode->predecessor());
}
echo $maze;
/lib/AStar.php
/**
* A simple AStar implementation
*/
class AStar
{
protected $openList;
protected $closedList;
/**
* Constructs the astar object
*/
public function __construct() {
$this->openList = new PriorityQueue;
$this->closedList = new SplObjectStorage;
}
public function getPath($startNode, $endNode) {
$this->openList->insert(0, $startNode);
while (!$this->openList->isEmpty()) {
$currentNode = $this->openList->extract();
if ($currentNode->equals($endNode)) {
return $currentNode;
}
$this->expandNode($currentNode, $endNode);
$this->closedList[$currentNode] = true;
}
return false;
}
protected function expandNode($currentNode, $endNode) {
foreach ($currentNode->successors() as $successor) {
if (isset($this->closedList[$successor])) {
continue;
}
$tentative_g = $currentNode->g() + $currentNode->distance($successor);
if ($this->openList->indexOf($successor) > -1 && $tentative_g >= $successor->g()) {
continue;
}
$successor->predecessor($currentNode);
$successor->g($tentative_g);
$f = $tentative_g + $successor->distance($endNode);
if ($this->openList->indexOf($successor) > -1) {
$this->openList->changeKey($successor, $f);
continue;
}
$this->openList->insert($f, $successor);
}
}
}
/lib/PriorityQueue.php
class PriorityQueue
{
protected $keys = array();
protected $values = array();
/**
* Helper function to swap two <key>/<value> pairs
*
* #param Integer a
* #param Integer b
* #return Integer b
*/
protected function swap($a, $b) {
// swap keys
$c = $this->keys[$a];
$this->keys[$a] = $this->keys[$b];
$this->keys[$b] = $c;
// swap values
$c = $this->values[$a];
$this->values[$a] = $this->values[$b];
$this->values[$b] = $c;
return $b;
}
/**
* Heapify up
*
* #param Integer pos
* #return void
*/
protected function upHeap($pos) {
while ($pos > 0) {
$parent = ($pos - 1) >> 2;
if ($this->compare($this->keys[$pos], $this->keys[$parent]) >= 0) {
break;
}
$pos = $this->swap($pos, $parent);
}
}
/**
* Heapify down
*
* #param Integer pos
* #return void
*/
protected function downHeap($pos) {
$len = sizeof($this->keys);
$max = ($len - 1) / 2;
while ($pos < $max) {
$child = 2 * $pos + 1;
if ($child < $len - 1 && $this->compare($this->keys[$child], $this->keys[$child + 1]) > 0) {
$child += 1;
}
if ($this->compare($this->keys[$pos], $this->keys[$child]) <= 0) {
break;
}
$pos = $this->swap($pos, $child);
}
}
/**
* Insert an <key>/<value> pair into the queue
*
* #param Object key
* #param Object value
* #return this
*/
public function insert($key, $value) {
$this->keys[] = $key;
$this->values[] = $value;
$this->upHeap(sizeof($this->keys) - 1);
return $this;
}
/**
* Extract the top <value>
*
* #return Object
*/
public function extract() {
$resultValue = $this->values[0];
$lastValue = array_pop($this->values);
$lastKey = array_pop($this->keys);
if (sizeof($this->keys) > 0) {
$this->values[0] = $lastValue;
$this->keys[0] = $lastKey;
$this->downHeap(0);
}
return $resultValue;
}
/**
* Changes the <key> of a <value>
*
* #param Object key
* #param Object value
* #return this
*/
public function changeKey($key, $value) {
$pos = $this->indexOf($value);
if ($pos !== false) {
$this->keys[$pos] = $key;
$this->upHeap($pos);
}
return $this;
}
/**
* Returns the index of <value> or false if <value> is not in the queue
*
* #return false|Int
*/
public function indexOf($value) {
return array_search($value, $this->values, true);
}
/**
* Used to campare two <key>s.
*
* #param Object a
* #param Object b
* #return Number
*/
protected function compare($a, $b) {
return $a - $b;
}
/**
* Returns true if the queue is empty
*
* #return Boolean
*/
public function isEmpty() {
return sizeof($this->keys) === 0;
}
}
/lib/Maze/Reader.php
class Maze_Reader implements IteratorAggregate
{
/**
* The initial maze
* #var string
*/
protected $rawMaze;
/**
* A tow dimensional array holding the parsed maze
* #var array
*/
protected $map = array();
/**
* A flat array holding all maze nodes
* #var array
*/
protected $nodes = array();
/**
* A value map for easier access
* #var array
*/
protected $valueMap = array();
/**
* Constructs a maze reader
*
* #param string $file A path to a maze file
*/
public function __construct($file) {
$this->rawMaze = file_get_contents($file);
$this->parseMaze($this->rawMaze);
}
/**
* Parses the raw maze into usable Maze_Nodes
*
* #param string $maze
*/
protected function parseMaze($maze) {
foreach (explode("\n", $maze) as $y => $row) {
foreach (str_split(trim($row)) as $x => $cellValue) {
if (!isset($this->map[$x])) {
$this->map[$x] = array();
}
if (!isset($this->valueMap[$cellValue])) {
$this->valueMap[$cellValue] = array();
}
$this->nodes[] = new Maze_Node($x, $y, $cellValue, $this);;
$this->map[$x][$y] =& $this->nodes[sizeof($this->nodes) - 1];
$this->valueMap[$cellValue][] =& $this->nodes[sizeof($this->nodes) - 1];
}
}
}
/**
* Returns the neighobrs of $node
*
* #return array
*/
public function getNeighbors(Maze_Node $node) {
$result = array();
$top = $node->y() - 1;
$right = $node->x() + 1;
$bottom = $node->y() + 1;
$left = $node->x() - 1;
// top left
if (isset($this->map[$left], $this->map[$left][$top])) {
$result[] = $this->map[$left][$top];
}
// top center
if (isset($this->map[$node->x()], $this->map[$node->x()][$top])) {
$result[] = $this->map[$node->x()][$top];
}
// top right
if (isset($this->map[$right], $this->map[$right][$top])) {
$result[] = $this->map[$right][$top];
}
// right
if (isset($this->map[$right], $this->map[$right][$node->y()])) {
$result[] = $this->map[$right][$node->y()];
}
// bottom right
if (isset($this->map[$right], $this->map[$right][$bottom])) {
$result[] = $this->map[$right][$bottom];
}
// bottom center
if (isset($this->map[$node->x()], $this->map[$node->x()][$bottom])) {
$result[] = $this->map[$node->x()][$bottom];
}
// bottom left
if (isset($this->map[$left], $this->map[$left][$bottom])) {
$result[] = $this->map[$left][$bottom];
}
// left
if (isset($this->map[$left], $this->map[$left][$node->y()])) {
$result[] = $this->map[$left][$node->y()];
}
return $result;
}
/**
* #IteratorAggregate
*/
public function getIterator() {
return new ArrayIterator($this->nodes);
}
/**
* Returns a node by value
*
* #param mixed $value
* #param boolean $returnOne
* #param mixed $fallback
* #return mixed
*/
public function getByValue($value, $returnOne = false, $fallback = array()) {
$result = isset($this->valueMap[$value]) ? $this->valueMap[$value] : $fallback;
if ($returnOne && is_array($result)) {
$result = array_shift($result);
}
return $result;
}
/**
* Simple output
*/
public function __toString() {
$result = array();
foreach ($this->map as $x => $col) {
foreach ($col as $y => $node) {
$result[$y][$x] = (string)$node;
}
}
return implode("\n", array_map('implode', $result));
}
}
/lib/Maze/Node.php
class Maze_Node
{
protected $x;
protected $y;
protected $value;
protected $maze;
protected $g;
protected $predecessor;
/**
* #param Integer $x
* #param Integer $y
* #param mixed $value
* #param Maze_Reader $maze
*/
public function __construct($x, $y, $value, $maze) {
$this->x = $x;
$this->y = $y;
$this->value = $value;
$this->maze = $maze;
}
/**
* Getter for x
*
* #return Integer
*/
public function x() {
return $this->x;
}
/**
* Getter for y
*
* #return Integer
*/
public function y() {
return $this->y;
}
/**
* Setter/Getter for g
*
* #param mixed $g
* #return mixed
*/
public function g($g = null) {
if ($g !== null) {
$this->g = $g;
}
return $this->g;
}
/**
* Setter/Getter for value
*
* #param mixed $value
* #return mixed
*/
public function value($value = null) {
if ($value !== null) {
$this->value = $value;
}
return $this->value;
}
/**
* Setter/Getter for predecessor
*
* #param Maze_Node $predecessor
* #return Maze_Node|null
*/
public function predecessor(Maze_Node $predecessor = null) {
if ($predecessor !== null) {
$this->predecessor = $predecessor;
}
return $this->predecessor;
}
/**
* simple distance getter
*
* #param Maze_Node $that
* #return Float
*/
public function distance(Maze_Node $that) {
if ($that->value() === 'W') {
return PHP_INT_MAX;
}
return sqrt(pow($that->x() - $this->x, 2) + pow($that->y() - $this->y, 2));
}
/**
* Test for equality
*
* #param Maze_Node $that
* #return boolean
*/
public function equals(Maze_Node $that) {
return $this == $that;
}
/**
* Returns the successors of this node
*
* #return array
*/
public function successors() {
return $this->maze->getNeighbors($this);
}
/**
* For debugging
*
* #return string
*/
public function __toString() {
return (string)$this->value;
}
}
I really need to be able to extract the metadata from a .ttf true type font file.
I'm building a central database of all the fonts all our designers use (they're forever swapping fonts via email to take over design elements, etc). I want to get all the fonts, some have silly names like 00001.ttf, so file name is no help, but I know the fonts have metadata, I need some way to extract that in PHP.
Then I can create a loop to look through the directories I've specified, get this data (and any other data I can get at the same time, and add it to a database.
I just really need help with the reading of this metadata part.
I came across this link. It will do what you want (I've tested it and posted results). Just pass the class the path of the TTF file you want to parse the data out of. then use $fontinfo[1].' '.$fontinfo[2] for the name.
In case you don't want to register, here is the class
Resulting Data
Array
(
[1] => Almonte Snow
[2] => Regular
[3] => RayLarabie: Almonte Snow: 2000
[4] => Almonte Snow
[5] => Version 2.000 2004
[6] => AlmonteSnow
[8] => Ray Larabie
[9] => Ray Larabie
[10] => Larabie Fonts is able to offer unique free fonts through the generous support of visitors to the site. Making fonts is my full-time job and every donation, in any amount, enables me to continue running the site and creating new fonts. If you would like to support Larabie Fonts visit www.larabiefonts.com for details.
[11] => http://www.larabiefonts.com
[12] => http://www.typodermic.com
)
Usage
<?php
include 'ttfInfo.class.php';
$fontinfo = getFontInfo('c:\windows\fonts\_LDS_almosnow.ttf');
echo '<pre>';
print_r($fontinfo);
echo '</pre>';
?>
ttfInfo.class.php
<?php
/**
* ttfInfo class
* Retrieve data stored in a TTF files 'name' table
*
* #original author Unknown
* found at http://www.phpclasses.org/browse/package/2144.html
*
* #ported for used on http://www.nufont.com
* #author Jason Arencibia
* #version 0.2
* #copyright (c) 2006 GrayTap Media
* #website http://www.graytap.com
* #license GPL 2.0
* #access public
*
* #todo: Make it Retrieve additional information from other tables
*
*/
class ttfInfo {
/**
* variable $_dirRestriction
* Restrict the resource pointer to this directory and above.
* Change to 1 for to allow the class to look outside of it current directory
* #protected
* #var int
*/
protected $_dirRestriction = 1;
/**
* variable $_dirRestriction
* Restrict the resource pointer to this directory and above.
* Change to 1 for nested directories
* #protected
* #var int
*/
protected $_recursive = 0;
/**
* variable $fontsdir
* This is to declare this variable as protected
* don't edit this!!!
* #protected
*/
protected $fontsdir;
/**
* variable $filename
* This is to declare this varable as protected
* don't edit this!!!
* #protected
*/
protected $filename;
/**
* function setFontFile()
* set the filename
* #public
* #param string $data the new value
* #return object reference to this
*/
public function setFontFile($data)
{
if ($this->_dirRestriction && preg_match('[\.\/|\.\.\/]', $data))
{
$this->exitClass('Error: Directory restriction is enforced!');
}
$this->filename = $data;
return $this;
} // public function setFontFile
/**
* function setFontsDir()
* set the Font Directory
* #public
* #param string $data the new value
* #return object referrence to this
*/
public function setFontsDir($data)
{
if ($this->_dirRestriction && preg_match('[\.\/|\.\.\/]', $data))
{
$this->exitClass('Error: Directory restriction is enforced!');
}
$this->fontsdir = $data;
return $this;
} // public function setFontsDir
/**
* function readFontsDir()
* #public
* #return information contained in the TTF 'name' table of all fonts in a directory.
*/
public function readFontsDir()
{
if (empty($this->fontsdir)) { $this->exitClass('Error: Fonts Directory has not been set with setFontsDir().'); }
if (empty($this->backupDir)){ $this->backupDir = $this->fontsdir; }
$this->array = array();
$d = dir($this->fontsdir);
while (false !== ($e = $d->read()))
{
if($e != '.' && $e != '..')
{
$e = $this->fontsdir . $e;
if($this->_recursive && is_dir($e))
{
$this->setFontsDir($e);
$this->array = array_merge($this->array, readFontsDir());
}
else if ($this->is_ttf($e) === true)
{
$this->setFontFile($e);
$this->array[$e] = $this->getFontInfo();
}
}
}
if (!empty($this->backupDir)){ $this->fontsdir = $this->backupDir; }
$d->close();
return $this;
} // public function readFontsDir
/**
* function setProtectedVar()
* #public
* #param string $var the new variable
* #param string $data the new value
* #return object reference to this
* DISABLED, NO REAL USE YET
public function setProtectedVar($var, $data)
{
if ($var == 'filename')
{
$this->setFontFile($data);
} else {
//if (isset($var) && !empty($data))
$this->$var = $data;
}
return $this;
}
*/
/**
* function getFontInfo()
* #public
* #return information contained in the TTF 'name' table.
*/
public function getFontInfo()
{
$fd = fopen ($this->filename, "r");
$this->text = fread ($fd, filesize($this->filename));
fclose ($fd);
$number_of_tables = hexdec($this->dec2ord($this->text[4]).$this->dec2ord($this->text[5]));
for ($i=0;$i<$number_of_tables;$i++)
{
$tag = $this->text[12+$i*16].$this->text[12+$i*16+1].$this->text[12+$i*16+2].$this->text[12+$i*16+3];
if ($tag == 'name')
{
$this->ntOffset = hexdec(
$this->dec2ord($this->text[12+$i*16+8]).$this->dec2ord($this->text[12+$i*16+8+1]).
$this->dec2ord($this->text[12+$i*16+8+2]).$this->dec2ord($this->text[12+$i*16+8+3]));
$offset_storage_dec = hexdec($this->dec2ord($this->text[$this->ntOffset+4]).$this->dec2ord($this->text[$this->ntOffset+5]));
$number_name_records_dec = hexdec($this->dec2ord($this->text[$this->ntOffset+2]).$this->dec2ord($this->text[$this->ntOffset+3]));
}
}
$storage_dec = $offset_storage_dec + $this->ntOffset;
$storage_hex = strtoupper(dechex($storage_dec));
for ($j=0;$j<$number_name_records_dec;$j++)
{
$platform_id_dec = hexdec($this->dec2ord($this->text[$this->ntOffset+6+$j*12+0]).$this->dec2ord($this->text[$this->ntOffset+6+$j*12+1]));
$name_id_dec = hexdec($this->dec2ord($this->text[$this->ntOffset+6+$j*12+6]).$this->dec2ord($this->text[$this->ntOffset+6+$j*12+7]));
$string_length_dec = hexdec($this->dec2ord($this->text[$this->ntOffset+6+$j*12+8]).$this->dec2ord($this->text[$this->ntOffset+6+$j*12+9]));
$string_offset_dec = hexdec($this->dec2ord($this->text[$this->ntOffset+6+$j*12+10]).$this->dec2ord($this->text[$this->ntOffset+6+$j*12+11]));
if (!empty($name_id_dec) and empty($font_tags[$name_id_dec]))
{
for($l=0;$l<$string_length_dec;$l++)
{
if (ord($this->text[$storage_dec+$string_offset_dec+$l]) == '0') { continue; }
else { $font_tags[$name_id_dec] .= ($this->text[$storage_dec+$string_offset_dec+$l]); }
}
}
}
return $font_tags;
} // public function getFontInfo
/**
* function getCopyright()
* #public
* #return 'Copyright notice' contained in the TTF 'name' table at index 0
*/
public function getCopyright()
{
$this->info = $this->getFontInfo();
return $this->info[0];
} // public function getCopyright
/**
* function getFontFamily()
* #public
* #return 'Font Family name' contained in the TTF 'name' table at index 1
*/
public function getFontFamily()
{
$this->info = $this->getFontInfo();
return $this->info[1];
} // public function getFontFamily
/**
* function getFontSubFamily()
* #public
* #return 'Font Subfamily name' contained in the TTF 'name' table at index 2
*/
public function getFontSubFamily()
{
$this->info = $this->getFontInfo();
return $this->info[2];
} // public function getFontSubFamily
/**
* function getFontId()
* #public
* #return 'Unique font identifier' contained in the TTF 'name' table at index 3
*/
public function getFontId()
{
$this->info = $this->getFontInfo();
return $this->info[3];
} // public function getFontId
/**
* function getFullFontName()
* #public
* #return 'Full font name' contained in the TTF 'name' table at index 4
*/
public function getFullFontName()
{
$this->info = $this->getFontInfo();
return $this->info[4];
} // public function getFullFontName
/**
* function dec2ord()
* Used to lessen redundant calls to multiple functions.
* #protected
* #return object
*/
protected function dec2ord($dec)
{
return $this->dec2hex(ord($dec));
} // protected function dec2ord
/**
* function dec2hex()
* private function to perform Hexadecimal to decimal with proper padding.
* #protected
* #return object
*/
protected function dec2hex($dec)
{
return str_repeat('0', 2-strlen(($hex=strtoupper(dechex($dec))))) . $hex;
} // protected function dec2hex
/**
* function dec2hex()
* private function to perform Hexadecimal to decimal with proper padding.
* #protected
* #return object
*/
protected function exitClass($message)
{
echo $message;
exit;
} // protected function dec2hex
/**
* function dec2hex()
* private helper function to test in the file in question is a ttf.
* #protected
* #return object
*/
protected function is_ttf($file)
{
$ext = explode('.', $file);
$ext = $ext[count($ext)-1];
return preg_match("/ttf$/i",$ext) ? true : false;
} // protected function is_ttf
} // class ttfInfo
function getFontInfo($resource)
{
$ttfInfo = new ttfInfo;
$ttfInfo->setFontFile($resource);
return $ttfInfo->getFontInfo();
}
?>
Update 2021
Here is an updated version of the class with some fixes
https://github.com/HusamAamer/TTFInfo.git
Very similar to the previously posted answer... I've been using this class for a long time now.
class fontAttributes extends baseClass
{
// --- ATTRIBUTES ---
/**
* #access private
* #var string
*/
private $_fileName = NULL ; // Name of the truetype font file
/**
* #access private
* #var string
*/
private $_copyright = NULL ; // Copyright
/**
* #access private
* #var string
*/
private $_fontFamily = NULL ; // Font Family
/**
* #access private
* #var string
*/
private $_fontSubFamily = NULL ; // Font SubFamily
/**
* #access private
* #var string
*/
private $_fontIdentifier = NULL ; // Font Unique Identifier
/**
* #access private
* #var string
*/
private $_fontName = NULL ; // Font Name
/**
* #access private
* #var string
*/
private $_fontVersion = NULL ; // Font Version
/**
* #access private
* #var string
*/
private $_postscriptName = NULL ; // Postscript Name
/**
* #access private
* #var string
*/
private $_trademark = NULL ; // Trademark
// --- OPERATIONS ---
private function _returnValue($inString)
{
if (ord($inString) == 0) {
if (function_exists('mb_convert_encoding')) {
return mb_convert_encoding($inString,"UTF-8","UTF-16");
} else {
return str_replace(chr(00),'',$inString);
}
} else {
return $inString;
}
} // function _returnValue()
/**
* #access public
* #return integer
*/
public function getCopyright()
{
return $this->_returnValue($this->_copyright);
} // function getCopyright()
/**
* #access public
* #return integer
*/
public function getFontFamily()
{
return $this->_returnValue($this->_fontFamily);
} // function getFontFamily()
/**
* #access public
* #return integer
*/
public function getFontSubFamily()
{
return $this->_returnValue($this->_fontSubFamily);
} // function getFontSubFamily()
/**
* #access public
* #return integer
*/
public function getFontIdentifier()
{
return $this->_returnValue($this->_fontIdentifier);
} // function getFontIdentifier()
/**
* #access public
* #return integer
*/
public function getFontName()
{
return $this->_returnValue($this->_fontName);
} // function getFontName()
/**
* #access public
* #return integer
*/
public function getFontVersion()
{
return $this->_returnValue($this->_fontVersion);
} // function getFontVersion()
/**
* #access public
* #return integer
*/
public function getPostscriptName()
{
return $this->_returnValue($this->_postscriptName);
} // function getPostscriptName()
/**
* #access public
* #return integer
*/
public function getTrademark()
{
return $this->_returnValue($this->_trademark);
} // function getTrademark()
/**
* Convert a big-endian word or longword value to an integer
*
* #access private
* #return integer
*/
private function _UConvert($bytesValue,$byteCount)
{
$retVal = 0;
$bytesLength = strlen($bytesValue);
for ($i=0; $i < $bytesLength; $i++) {
$tmpVal = ord($bytesValue{$i});
$t = pow(256,($byteCount-$i-1));
$retVal += $tmpVal*$t;
}
return $retVal;
} // function UConvert()
/**
* Convert a big-endian word value to an integer
*
* #access private
* #return integer
*/
private function _USHORT($stringValue) {
return $this->_UConvert($stringValue,2);
}
/**
* Convert a big-endian word value to an integer
*
* #access private
* #return integer
*/
private function _ULONG($stringValue) {
return $this->_UConvert($stringValue,4);
}
/**
* Read the Font Attributes
*
* #access private
* #return integer
*/
private function readFontAttributes() {
$fontHandle = fopen($this->_fileName, "rb");
// Read the file header
$TT_OFFSET_TABLE = fread($fontHandle, 12);
$uMajorVersion = $this->_USHORT(substr($TT_OFFSET_TABLE,0,2));
$uMinorVersion = $this->_USHORT(substr($TT_OFFSET_TABLE,2,2));
$uNumOfTables = $this->_USHORT(substr($TT_OFFSET_TABLE,4,2));
// $uSearchRange = $this->_USHORT(substr($TT_OFFSET_TABLE,6,2));
// $uEntrySelector = $this->_USHORT(substr($TT_OFFSET_TABLE,8,2));
// $uRangeShift = $this->_USHORT(substr($TT_OFFSET_TABLE,10,2));
// Check is this is a true type font and the version is 1.0
if ($uMajorVersion != 1 || $uMinorVersion != 0) {
fclose($fontHandle);
throw new Exception($this->_fileName.' is not a Truetype font file') ;
}
// Look for details of the name table
$nameTableFound = false;
for ($t=0; $t < $uNumOfTables; $t++) {
$TT_TABLE_DIRECTORY = fread($fontHandle, 16);
$szTag = substr($TT_TABLE_DIRECTORY,0,4);
if (strtolower($szTag) == 'name') {
// $uCheckSum = $this->_ULONG(substr($TT_TABLE_DIRECTORY,4,4));
$uOffset = $this->_ULONG(substr($TT_TABLE_DIRECTORY,8,4));
// $uLength = $this->_ULONG(substr($TT_TABLE_DIRECTORY,12,4));
$nameTableFound = true;
break;
}
}
if (!$nameTableFound) {
fclose($fontHandle);
throw new Exception('Can\'t find name table in '.$this->_fileName) ;
}
// Set offset to the start of the name table
fseek($fontHandle,$uOffset,SEEK_SET);
$TT_NAME_TABLE_HEADER = fread($fontHandle, 6);
// $uFSelector = $this->_USHORT(substr($TT_NAME_TABLE_HEADER,0,2));
$uNRCount = $this->_USHORT(substr($TT_NAME_TABLE_HEADER,2,2));
$uStorageOffset = $this->_USHORT(substr($TT_NAME_TABLE_HEADER,4,2));
$attributeCount = 0;
for ($a=0; $a < $uNRCount; $a++) {
$TT_NAME_RECORD = fread($fontHandle, 12);
$uNameID = $this->_USHORT(substr($TT_NAME_RECORD,6,2));
if ($uNameID <= 7) {
// $uPlatformID = $this->_USHORT(substr($TT_NAME_RECORD,0,2));
$uEncodingID = $this->_USHORT(substr($TT_NAME_RECORD,2,2));
// $uLanguageID = $this->_USHORT(substr($TT_NAME_RECORD,4,2));
$uStringLength = $this->_USHORT(substr($TT_NAME_RECORD,8,2));
$uStringOffset = $this->_USHORT(substr($TT_NAME_RECORD,10,2));
if ($uStringLength > 0) {
$nPos = ftell($fontHandle);
fseek($fontHandle,$uOffset + $uStringOffset + $uStorageOffset,SEEK_SET);
$testValue = fread($fontHandle, $uStringLength);
if (trim($testValue) > '') {
switch ($uNameID) {
case 0 : if ($this->_copyright == NULL) {
$this->_copyright = $testValue;
$attributeCount++;
}
break;
case 1 : if ($this->_fontFamily == NULL) {
$this->_fontFamily = $testValue;
$attributeCount++;
}
break;
case 2 : if ($this->_fontSubFamily == NULL) {
$this->_fontSubFamily = $testValue;
$attributeCount++;
}
break;
case 3 : if ($this->_fontIdentifier == NULL) {
$this->_fontIdentifier = $testValue;
$attributeCount++;
}
break;
case 4 : if ($this->_fontName == NULL) {
$this->_fontName = $testValue;
$attributeCount++;
}
break;
case 5 : if ($this->_fontVersion == NULL) {
$this->_fontVersion = $testValue;
$attributeCount++;
}
break;
case 6 : if ($this->_postscriptName == NULL) {
$this->_postscriptName = $testValue;
$attributeCount++;
}
break;
case 7 : if ($this->_trademark == NULL) {
$this->_trademark = $testValue;
$attributeCount++;
}
break;
}
}
fseek($fontHandle,$nPos,SEEK_SET);
}
}
if ($attributeCount > 7) {
break;
}
}
fclose($fontHandle);
return true;
}
/**
* #access constructor
* #return void
*/
function __construct($fileName='') {
if ($fileName == '') {
throw new Exception('Font File has not been specified') ;
}
$this->_fileName = $fileName;
if (!file_exists($this->_fileName)) {
throw new Exception($this->_fileName.' does not exist') ;
} elseif (!is_readable($this->_fileName)) {
throw new Exception($this->_fileName.' is not a readable file') ;
}
return $this->readFontAttributes();
} // function constructor()
} /* end of class fontAttributes */
Why reinvent the wheel when the fine people at DOMPDF project has already done the work for you? Take a look at php-font-lib # https://github.com/PhenX/php-font-lib. This has all the features that you have asked for and supports other font formats as well. Look at the demo UI # http://pxd.me/php-font-lib/www/font_explorer.html to get an idea about what kind of information you can get from this library.
I'm using Netbeans 6.9 and writing a PHP class that implements the Iterator interface. I would like to have the IDE offer Intellisense as I iterate over the items in my object. It seems to work for the Zend Framework as I've noticed that when iterating over a Zend_Db_Rowset I get intellisense for a Zend_DB_Row. For example, when I write:
foreach($rowset as $row) {
$row->delete();
}
When I type "$row->" Netbeans pops up its code hints for the member functions of Zend_Db_Row_Abstract. Unfortunately, I can't get this to work for my own code. Below is a sample I tried to get to work:
class Foo {
private $value;
/**
*
* #param string $value
*/
public function setValue($value) {
$this->value = $value;
}
/**
*
* #return string
*/
public function getValue() {
return $this->value;
}
}
class It implements Iterator {
private $data;
public function __construct($data) {
$this->data = $data;
}
/**
*
* #return Foo
*/
public function current() {
return current($this->data);
}
/**
*
* #return Foo
*/
public function key() {
return key($this->data);
}
/**
*
* #return Foo
*/
public function next() {
return next($this->data);
}
/**
*
* #return Foo
*/
public function rewind() {
return reset($this->data);
}
/**
*
* #return bool
*/
public function valid() {
return key($this->data) !== null;
}
}
$a = new Foo();
$b = new Foo();
$a->setValue('Hello');
$b->setValue('Bye');
$testData = array($a, $b);
$myIt = new It($testData);
foreach ($myIt as $obj) {
echo $obj->getValue();
}
Strangely the intellisense seems to think $obj is an object of type It when I want it to think (and it actually is) an object of type Foo.
Within the body of the loop you can provide the type hint in a comment.
/* #var $obj Foo */
+1 for Brian Fisher's suggestion.