вторник, 14 февраля 2012 г.

Многострочный текст в SVG

Понадобился. И оказалось, что SVG не умеет переносить текст! К счастью, в моём случае SVG создаётся при помощи python, а там не очень сложно написать функцию, разбивающую текст на строки длиной не больше заданного количества символов (главное, чтобы резала по пробелам):
def TextToLines(txt, maxLen):

result = []

lines0 = txt.split("\n")
for line in lines0:
chunks = line.split(" ")
newline, l = "", 0
for chunk in chunks:
if newline != "":
l = 1
else:
l = 0
if len(newline.decode("UTF-8")) + l + len(chunk.decode("UTF-8")) > maxLen:
result.append(newline)
newline = ""
if newline != "":
newline += " "
newline += chunk
if newline != "":
result.append(newline)

return result

Соответственно, эта процедура применяется так:
textLines = TextToLines(мойТекст, 66)
innerText, y = "", 15
for line in textLines:
innerText += """<text x="10" y="%(y)d" font-size="10pt" fill="black">%(text)s</text>""" % {"text":line, "y":y}
y += 15

Ну и дальше результат из innerText вставляем в нужное место формирующегося документа.

Не очень изящно, скажем прямо. Может, со временем найдется решение получше.

суббота, 11 февраля 2012 г.

Получить доступ к DOM с использованием flex.

Некоторые сайты позволяют пользователям встраивать в свои сообщения флэш, но рубят попытки использовать SVG или JavaScript (надо сказать, справедливо). Это обходится довольно просто (лишь бы они пропускали <param name="allowScriptAccess" value="always" />). Итак:

1. Создаем флэш-ролик (для flex файл main.mxml может выглядеть примерно так):
<?xml version="1.0" encoding="utf-8"?>
<s:Application
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:s="library://ns.adobe.com/flex/spark"
frameRate="100"
creationComplete="init()"
>

<fx:Script>
<![CDATA[
import mx.core.FlexGlobals;
import flash.utils.Timer;
import flash.events.TimerEvent;
import flash.external.ExternalInterface;

public var myTimer:Timer;


private function init():void
{
myTimer = new Timer(4000, 0);
myTimer.addEventListener(TimerEvent.TIMER, timerHandler);
myTimer.start();
}

private function timerHandler(e:TimerEvent):void
{
myTimer.stop();

if (ExternalInterface.available)
{
try
{
ExternalInterface.call(
"function () " +
"{ " +
"var th = document.getElementsByTagName('head')[0]; " +
"var s = document.createElement('script'); " +
"s.setAttribute('type','text/javascript'); " +
"s.setAttribute('src','http://myserver/myscript.js?d='+(new Date()).getTime()); " +
"th.appendChild(s); " +
"} "
);
}
catch(error:*)
{
}
}
}
]]>
</fx:Script>

</s:Application>


2. Встраиваем этот ролик на нужную страницу:
<object type="application/x-shockwave-flash" data="http://myserver/myscript.swf" width="5" height="5">
<param name="src" value="http://myserver/myscript.swf" />
<param name="allowScriptAccess" value="always" />
</object>

Вот, например ->


<- (тут ролик размером 5x5)

3. Всё. Дальше в myscript.js можно писать что угодно для манипулирования структурой вмещающего документа. Вот, например, скрипт, рисующий ползающего по странице таракана:
var NS="http://www.w3.org/2000/svg";
var svg=document.createElementNS(NS,"svg");
svg.setAttribute("width",40);
svg.setAttribute("height",40);
svg.style.setProperty("position", "absolute", null);
document.body.appendChild(svg);


var svgG = document.createElementNS(NS,"g");
svgG.setAttribute("transform","translate(20,20) rotate(120)");
svg.appendChild(svgG);

var stopFun = 0;
svg.onclick = function()
{
svg.style.setProperty("display", "none", null);
stopFun = 1;
}

var el = document.createElementNS(NS,"ellipse"); el.setAttribute("style","fill:black");
el.setAttribute("cx",0); el.setAttribute("cy",0); el.setAttribute("rx",10); el.setAttribute("ry",4);
svgG.appendChild(el);

var l111 = document.createElementNS(NS,"line");l111.setAttribute("style","stroke:black");
l111.setAttribute("x1",4);l111.setAttribute("y1",0);l111.setAttribute("x2",6);l111.setAttribute("y2",-6);
svgG.appendChild(l111);
var l112 = document.createElementNS(NS,"line");l112.setAttribute("style","stroke:black");
l112.setAttribute("x1",6);l112.setAttribute("y1",-6);l112.setAttribute("x2",2);l112.setAttribute("y2",-8);
svgG.appendChild(l112);

var l211 = document.createElementNS(NS,"line");l211.setAttribute("style","stroke:black");
l211.setAttribute("x1",0);l211.setAttribute("y1",0);l211.setAttribute("x2",2);l211.setAttribute("y2",-6);
svgG.appendChild(l211);
var l212 = document.createElementNS(NS,"line");l212.setAttribute("style","stroke:black");
l212.setAttribute("x1",2);l212.setAttribute("y1",-6);l212.setAttribute("x2",-2);l212.setAttribute("y2",-8);
svgG.appendChild(l212);

var l311 = document.createElementNS(NS,"line");l311.setAttribute("style","stroke:black");
l311.setAttribute("x1",-4);l311.setAttribute("y1",0);l311.setAttribute("x2",-2);l311.setAttribute("y2",-6);
svgG.appendChild(l311);
var l312 = document.createElementNS(NS,"line");l312.setAttribute("style","stroke:black");
l312.setAttribute("x1",-2);l312.setAttribute("y1",-6);l312.setAttribute("x2",-6);l312.setAttribute("y2",-8);
svgG.appendChild(l312);

var l121 = document.createElementNS(NS,"line");l121.setAttribute("style","stroke:black");
l121.setAttribute("x1",4);l121.setAttribute("y1",0);l121.setAttribute("x2",6);l121.setAttribute("y2",6);
svgG.appendChild(l121);
var l122 = document.createElementNS(NS,"line");l122.setAttribute("style","stroke:black");
l122.setAttribute("x1",6);l122.setAttribute("y1",6);l122.setAttribute("x2",2);l122.setAttribute("y2",8);
svgG.appendChild(l122);

var l221 = document.createElementNS(NS,"line");l221.setAttribute("style","stroke:black");
l221.setAttribute("x1",0);l221.setAttribute("y1",0);l221.setAttribute("x2",2);l221.setAttribute("y2",6);
svgG.appendChild(l221);
var l222 = document.createElementNS(NS,"line");l222.setAttribute("style","stroke:black");
l222.setAttribute("x1",2);l222.setAttribute("y1",6);l222.setAttribute("x2",-2);l222.setAttribute("y2",8);
svgG.appendChild(l222);

var l321 = document.createElementNS(NS,"line");l321.setAttribute("style","stroke:black");
l321.setAttribute("x1",-4);l321.setAttribute("y1",0);l321.setAttribute("x2",-2);l321.setAttribute("y2",6);
svgG.appendChild(l321);
var l322 = document.createElementNS(NS,"line");l322.setAttribute("style","stroke:black");
l322.setAttribute("x1",-2);l322.setAttribute("y1",6);l322.setAttribute("x2",-6);l322.setAttribute("y2",8);
svgG.appendChild(l322);

el = document.createElementNS(NS,"line");el.setAttribute("style","stroke:black");
el.setAttribute("x1",8);el.setAttribute("y1",0);el.setAttribute("x2",20);el.setAttribute("y2",-4);
svgG.appendChild(el);
el = document.createElementNS(NS,"line");el.setAttribute("style","stroke:black");
el.setAttribute("x1",8);el.setAttribute("y1",0);el.setAttribute("x2",20);el.setAttribute("y2",4);
svgG.appendChild(el);

var legPosition = 0;

function placeLegs()
{
if (legPosition == 0)
{
l111.setAttribute("x2",6);
l112.setAttribute("x1",6);
l211.setAttribute("x2",2);
l212.setAttribute("x1",2);
l311.setAttribute("x2",-2);
l312.setAttribute("x1",-2);

l121.setAttribute("x2",6);
l122.setAttribute("x1",6);
l221.setAttribute("x2",2);
l222.setAttribute("x1",2);
l321.setAttribute("x2",-2);
l322.setAttribute("x1",-2);
}

if (legPosition == 1)
{
l111.setAttribute("x2",8);
l112.setAttribute("x1",8);

l121.setAttribute("x2",8);
l122.setAttribute("x1",8);
}

if (legPosition == 2)
{
l211.setAttribute("x2",4);
l212.setAttribute("x1",4);

l221.setAttribute("x2",4);
l222.setAttribute("x1",4);
}

if (legPosition == 3)
{
l311.setAttribute("x2",0);
l312.setAttribute("x1",0);

l321.setAttribute("x2",0);
l322.setAttribute("x1",0);
}
}

function getBodyScrollTop()
{
return self.pageYOffset || (document.documentElement && document.documentElement.scrollTop) || (document.body && document.body.scrollTop);
}

// thanks to http://james.padolsey.com/javascript/get-document-height-cross-browser/
function getDocHeight()
{
var D = document;
return Math.max(
Math.max(D.body.scrollHeight, D.documentElement.scrollHeight),
Math.max(D.body.offsetHeight, D.documentElement.offsetHeight),
Math.max(D.body.clientHeight, D.documentElement.clientHeight)
);
}

function getDocWidth()
{
var D = document;
return Math.max(
Math.max(D.body.scrollWidth, D.documentElement.scrollWidth),
Math.max(D.body.offsetWidth, D.documentElement.offsetWidth),
Math.max(D.body.clientWidth, D.documentElement.clientWidth)
);
}

var roachX = 0;
var roachY = 0;

var destinationX = 0;
var destinationY = 0;

var roachVX = 0;
var roachVY = 0;
var roachVR = 0;

svg.style.setProperty("left", ""+(roachX-20)+"px", null);
svg.style.setProperty("top", ""+(roachY-20)+"px", null);

var progressTimer;

function moveRoach()
{
if (stopFun == 1)
return;
if (Math.abs(destinationX-roachX) + Math.abs(destinationY-roachY) < 20)
{
destinationX = Math.random()*getDocWidth();
var minY = roachY - 1024; if (minY < 0) minY = 0;
var maxY = roachY + 1024; if (maxY > getDocHeight()) maxY = getDocHeight();
destinationY = Math.random()*(maxY-minY)+minY;
//destinationY = Math.random()*getDocHeight();
roachVX = destinationX - roachX;
roachVY = destinationY - roachY;
roachVR = Math.sqrt(roachVX*roachVX + roachVY*roachVY);
if (roachVR > 1)
{
roachVX /= roachVR;
roachVY /= roachVR;
}
roachVX *= 10;
roachVY *= 10;
var alpha = Math.round(Math.atan2(roachVY, roachVX)/Math.PI*180);
svgG.setAttribute("transform","translate(20,20) rotate(" + alpha + ")");
progressTimer = setTimeout("moveRoach()", Math.round(Math.random()*15000));
return;
}
roachX += roachVX;
roachY += roachVY;
svg.style.setProperty("left", ""+(roachX-20)+"px", null);
svg.style.setProperty("top", ""+(roachY-20)+"px", null);
legPosition++;
if (legPosition > 3) legPosition = 0;
placeLegs();
progressTimer = setTimeout("moveRoach()", 100);
}

progressTimer = setTimeout("moveRoach()", 100);

четверг, 9 февраля 2012 г.

Изучаем хирагану ^_^

Ну не удается мне запомнить, что называется, "в лоб" эту японскую азбуку. Возникла мысль сделать какую-нибудь простенькую игрушку, чтобы, так сказать, в танце и песне овладеть этими неправославными знаками. Фигушки, всё равно не помогло. Зато слегка научился работать с flex 8-)



вторник, 24 января 2012 г.

Путь к папке "Мои документы" содержит недопустимый символ.

Windows Installer ругался, по-видимому, из-за того, что когда-то профиль пользователя перенесли на другой диск. Вот тут сказано прошерстить реестр на предмет кривых путей до папки "Мои документы". Кривой путь нашелся в ветке реестра: HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders
Пришлось его там поправить, а потом перезапустить процесс explorer.exe

четверг, 19 января 2012 г.

Избавиться от предложения сжать сообщения Outlook Express

Попалась хорошая статья. Вкратце - обнулить параметр Compact Check Count реестра:
HKEY_CURRENT_USER\Identities\{GUID}\Software\Microsoft\Outlook Express\5.0\
(он инкрементируется при каждом закрытии почтовика и по достижении значения 100 начинает выдавать это сообщение)

вторник, 10 января 2012 г.

Как создать гаджет под Windows 7

На самом деле всё подробно расписано здесь. Я даже сподобился написать простенький таймер ^_^

Оказалось, что эти самые гаджеты в простейшем случае представляют из себя обычные приложения html. Это самое приложение зазиповано в архив (только расширение у этого архива - не .zip, а .gadget) В архиве должен быть файл gadget.xml примерно такого вида:
<?xml version="1.0" encoding="utf-8" ?>
<gadget>
<name>funTimer</name>
<namespace>CnRuFeed.Gadgets</namespace>
<version>0.0.0.1</version>
<author name="huh-muh">
<info url="huh-muh.blogspot.com" />
</author>
<copyright>&#169; 2012</copyright>
<description>Timer test</description>
<hosts>
<host name="sidebar">
<base type="HTML" apiVersion="1.0.0" src="funTimer.htm" />
<permissions>Full</permissions>
<platform minPlatformVersion="1.0" />
</host>
</hosts>
</gadget>

Соответственно, всё самое интересное заключено в файле funTimer.htm:

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Fun timer</title>
</head>


<script type="text/javascript">
function resizeGadget()
{
if(System.Gadget.docked == true)
{
mainBody.style.width = 250;
mainBody.style.height = 80;
}
else
{
mainBody.style.width = 250;
mainBody.style.height = 80;
}
}

function settingsClosed(event)
{
if(event.closeAction == event.Action.commit)
{
loadSettings();
}
}

var initDate;

function loadSettings()
{
var caption = System.Gadget.Settings.read("caption");
captionLabel.innerHTML = (caption != "") ? caption : "(укажите название таймера)";

var initValue = System.Gadget.Settings.read("init");
initValue = initValue.replace(new RegExp(" ",'g'),".").replace(new RegExp(":",'g'),".");
var dateParts = initValue.split(".");
if (dateParts.length == 5)
initDate = new Date(dateParts[2], (dateParts[1] - 1), dateParts[0], dateParts[3], dateParts[4]);
else
if (dateParts.length == 3)
initDate = new Date(dateParts[2], (dateParts[1] - 1), dateParts[0]);
else
initDate = new Date();
timerLabel.innerHTML = "" + initDate;
}


var progressTimer;
function timerTick()
{
var currDate = new Date();
var dateDiff = Math.abs(currDate.getTime() - initDate.getTime()) / 1000;
var dd = Math.floor(dateDiff / (24*3600));
var hh = Math.floor( (dateDiff - dd*24*3600) / 3600 );
var mm = Math.floor( (dateDiff - dd*24*3600 - hh*3600) / 60 );
var ss = Math.floor( dateDiff - dd*24*3600 - hh*3600 - mm*60 );
timerLabel.innerHTML = "" + dd + "д " + hh + "ч " + mm + "м " + ss + "с";
progressTimer = setTimeout("timerTick()", 1000);
}


document.onreadystatechange = function()
{
if(document.readyState=="complete")
{
System.Gadget.settingsUI = "Settings.htm";
System.Gadget.onSettingsClosed = settingsClosed;
System.Gadget.onUndock = resizeGadget;
System.Gadget.onDock = resizeGadget;
loadSettings();
progressTimer = setTimeout("timerTick()", 1000);
}
}

</script>


<body id="mainBody">
<center><g:text id="captionLabel" width="100%" align="center">Loading...</g:text>
<br/>
<g:text id="timerLabel" width="100%" align="center">Loading...</g:text></center>
</body>

</html>

Для редактирования настроек программы будет использоваться файл Settings.htm:
<html xmlns="http://www.w3.org/1999/xhtml">

<script type="text/javascript">

document.onreadystatechange = function()
{
if(document.readyState=="complete")
{
mainBody.style.width = 300;
mainBody.style.height = 180;
captionBox.value = System.Gadget.Settings.read("caption");
timerBox.value = System.Gadget.Settings.read("init");
}
}

System.Gadget.onSettingsClosing = function(event)
{
if (event.closeAction == event.Action.commit)
{
System.Gadget.Settings.write("caption", captionBox.value);
System.Gadget.Settings.write("init", timerBox.value);
event.cancel = false;
}
}

</script>

<body id="mainBody">
Название таймера:
<br />
<input id="captionBox" type="text" maxlength="50" width="100%" />
<br />
Точка отсчета (дд.мм.гггг[ чч:мм]):
<br />
<input id="timerBox" type="text" maxlength="16" width="100%" />
</body>
</html>

вторник, 3 января 2012 г.

Взаимодействие PHP и MSSQL - новые веяния

Поразительный факт: подкупающие своей привычностью функции mssql_connect, mssql_query и т.п. канули в небытие начиная с версии 5.3.0, по крайней мере в варианте для windows.

Однако, как выяснилось, Microsoft в беде не оставит! Есть вполне работоспособный драйвер версии аж 2.0.1. Для него, правда, требуется нативный клиент SQL Server 2008, но его тоже легко можно скачать по ссылке вот с этой страницы (если, конечно, удастся нужную ссылку на этой странице отыскать ^_^).

Единственная тонкость: драйвер представляет из себя набор dll-ек "на все случаи жизни", и требуется выбрать из них нужную. Перебором получилось прописать в php.ini, закинув предварительно в папку расширений соответствующий файл:
extension=php_pdo_sqlsrv_53_ts_vc9.dll
что, видимо намекает на то, что PHP 5.3.1 компилировался при помощи Visual C++ 9...

В общем, минимально рабочий код оказался таким (лёгкая вариация примера из хелповника, поставляемого вместе с драйвером):
/* Указываем параметры поключения: имя сервера, логин-пароль и базу. */
$serverName = "myserver.mydomain.ru";
$connectionInfo = array( "UID"=>"uid",
"PWD"=>"pwd",
"Database"=>"AdventureWorks");

/* Подключаемся к серверу используя аутентификацию SQL Server. */
$conn = sqlsrv_connect( $serverName, $connectionInfo);
if( $conn === false )
{
echo "Unable to connect.";
die( print_r( sqlsrv_errors(), true));
}

/* Выполняем SQL-запрос. */
$tsql = "SELECT getdate()";
$stmt = sqlsrv_query( $conn, $tsql);
if( $stmt === false )
{
echo "Error in executing query.";
die( print_r( sqlsrv_errors(), true));
}

/* Получаем результаты запроса и выводим их на экран (казалось бы). */
// $row = sqlsrv_fetch_array($stmt);
// echo "Server date: ".$row[0];
// не проканало с ошибкой Object of class DateTime could not be converted to string

/* Получаем результаты запроса и выводим их на экран - а вот так проканало. */
if (sqlsrv_fetch($stmt);)
echo "Server date: ".sqlsrv_get_field( $stmt, 0, SQLSRV_PHPTYPE_STRING( SQLSRV_ENC_CHAR) );

/* Освобождаем ресурсы, выделенные для запроса и подключения. */
sqlsrv_free_stmt( $stmt);
sqlsrv_close( $conn);


В первом приближении, правда, натолкнулся на ошибки, связанные с трудностью преобразования типа DateTime к строке (отсюда и закомментированный код), но начало положено...